ข้ามไปยังเนื้อหา

Proxy

Proxy คือตัวแทน (placeholder) ของ object อีกตัว ใช้ interface เดียวกันและคุมทางเข้าถึงเอาไว้ proxy จึงตัดสินใจได้ว่าจะแตะ object จริงหรือไม่ ตอนไหน และอย่างไร

บางครั้งคุณอยากใช้ object ตัวหนึ่ง แต่การเข้าถึงตรง ๆ มีต้นทุนสูง อ่อนไหว หรืออยู่ไกลออกไป รูปความละเอียดสูงไม่ควรโหลดจากดิสก์จนกว่าจะมีใครสั่งแสดงจริง ๆ operation ที่ต้องใช้สิทธิ์พิเศษควรรันให้เฉพาะผู้เรียกที่ได้รับอนุญาต และ remote service ควรเข้าถึงผ่านตัวแทนในเครื่อง ทุกกรณีที่ว่ามา ตัว object จริงไม่ได้มีปัญหาอะไร สิ่งที่ขาดคือชั้นควบคุมทางเข้าออกต่างหาก

Proxy คือชั้นนั้น โดย implement interface เดียวกับ subject ตัวจริง client จึงแยกไม่ออกว่ากำลังคุยกับใคร และภายในก็ถือ reference ไปยัง object จริง (หรือรู้วิธีไปเอามา) ทุกครั้งที่ถูกเรียก proxy จะแทรกงานของตัวเองก่อนได้ เช่น สร้าง subject แบบ lazy ตรวจสิทธิ์ cache ผลลัพธ์ หรือบันทึกการเข้าถึง แล้วค่อยส่งต่อไปยัง object จริง หรือปฏิเสธไปเลย เพราะ proxy ใช้แทน subject ได้ คุณจึงสอดการควบคุมพวกนี้เข้าไปได้โดยไม่ต้องแก้ code ฝั่ง client เลยสักบรรทัด

classDiagram
  class Image {
    <<interface>>
    +display() string
  }
  class RealImage {
    -file: string
    +display() string
  }
  class LazyImageProxy {
    -file: string
    -real: RealImage
    +display() string
  }
  class Client
  Image <|.. RealImage
  Image <|.. LazyImageProxy
  LazyImageProxy --> RealImage : creates and forwards on demand
  Client --> Image : uses
proxy ใช้ interface ของ subject ร่วมกันและควบคุมการเข้าถึง object จริง
  • Subject (Image) — interface ที่ object จริงกับ proxy ใช้ร่วมกัน เพื่อให้สลับแทนกันได้
  • Real Subject (RealImage) — object จริงที่ทำงานและมีต้นทุนสูงหรืออ่อนไหวต่อการเข้าถึง
  • Proxy (LazyImageProxy) — implement subject interface ควบคุมการเข้าถึง และส่งต่อไปยัง real subject เมื่อเหมาะสม
  • Client — คุยผ่าน subject interface โดยไม่รู้ว่าปลายทางเป็น proxy หรือ object จริง

virtual proxy ที่หน่วงการโหลดรูปภาพหนักไว้จนกว่าจะมีการเรียก display ครั้งแรก แล้วจึง cache object ที่โหลดแล้วไว้ใช้ซ้ำ

interface Image {
display(): string;
}
class RealImage implements Image {
constructor(private readonly file: string) {
// Pretend this reads a large file from disk.
console.log(`loading ${file}`);
}
display = () => `showing ${this.file}`;
}
class LazyImageProxy implements Image {
private real: RealImage | null = null;
constructor(private readonly file: string) {}
display(): string {
if (this.real === null) {
this.real = new RealImage(this.file); // load only on first use
}
return this.real.display();
}
}
const image: Image = new LazyImageProxy('photo.png'); // nothing loaded yet
console.log(image.display()); // loading photo.png \n showing photo.png
console.log(image.display()); // showing photo.png (cached, no reload)

ทั้งสามตัวห่อของเหมือนกัน แต่จุดประสงค์คนละเรื่อง Decorator ใช้ interface ของ subject ร่วมกันและ เพิ่มพฤติกรรม ให้ผลลัพธ์ของแต่ละการเรียกสมบูรณ์ขึ้น คุณจึงซ้อน decorator หลายชั้นเพื่อประกอบ feature ได้

ส่วน Proxy ใช้ interface เดียวกันเหมือนกัน แต่หน้าที่คือ ควบคุมการเข้าถึง คือตัดสินใจว่าการเรียกจริงจะเกิดขึ้นหรือไม่ (lazy loading, ตรวจสิทธิ์, caching) ไม่ได้ไปเสริมค่าที่ส่งกลับ และมักดูแล lifecycle ของ real subject ตัวเดียว

Facade ไม่ทำทั้งสองอย่าง แต่ออกแบบ interface ใหม่ที่ง่ายกว่า ครอบ subsystem ทั้งก้อนที่มี class เต็มไปหมด ดังนั้น facade จึงไม่ได้ใช้ interface ของ subject ร่วมกัน และใช้แทน object เบื้องล่างตัวใดตัวหนึ่งไม่ได้

  • ข้อดี: ควบคุมการเข้าถึงอย่างโปร่งใส คือ lazy loading การควบคุมการเข้าถึง การ caching หรือ remoting โดยไม่ต้องเปลี่ยน client
  • ข้อดี: หน่วงหรือหลีกเลี่ยงงานที่มีต้นทุนสูงจนกว่าจะจำเป็นจริง ๆ (virtual proxy)
  • ข้อดี: รวมงาน cross-cutting อย่างการตรวจสิทธิ์หรือ logging ไว้ที่จุดเดียว
  • ข้อเสีย: เพิ่มชั้นของ indirection ที่อาจบดบังว่างานหรือ latency เกิดขึ้นที่ไหนจริง ๆ
  • ข้อเสีย: proxy แบบ lazy หรือ remote สร้างความประหลาดใจเรื่องจังหวะเวลา คือการเรียกครั้งแรกทำงานต่างจากครั้งต่อ ๆ มามาก
  • Decorator ใช้รูปแบบการห่อร่วมกันแต่เพิ่มพฤติกรรมแทนที่จะควบคุมการเข้าถึง
  • Facade ทำให้ subsystem เรียบง่ายไว้หลัง interface ใหม่ ขณะที่ proxy คงไว้ซึ่ง interface เดียวกับ subject ตัวเดียว
  • Adapter เปลี่ยน interface ให้ตรงกับ client ส่วน proxy จงใจคงไว้ซึ่ง interface ที่เหมือนเดิม
ProxyDecoratorAdapter
จุดประสงค์ควบคุม access ไปยัง objectเพิ่ม behavior โดยไม่เปลี่ยน interfaceแปลง interface ให้ตรงกัน
client รู้ไหมไม่รู้ (interface เดียวกัน)ไม่รู้ (interface เดียวกัน)ไม่รู้ (interface ใหม่)
ประเภทที่พบบ่อยvirtual, protection, remote, cachelogging, validation, cachinglegacy wrapper, API bridge
ตัวอย่างES6 Proxy, lazy image loadstream.pipe(), middlewareJDBC driver, fetch polyfill
จุดประสงค์หลักของ Proxy คืออะไร?
virtual proxy (อย่าง lazy image) ปรับให้เหมาะสมในเรื่องอะไร?
Proxy แตกต่างจาก Decorator อย่างไร?
ทำไม Proxy จึงใช้แทน real subject ได้?