- 1. ปัญหาที่ Proxy Pattern ช่วยแก้
- 2. Proxy Pattern คืออะไร
- 3. องค์ประกอบของรูปแบบ
- 4. ประเภทของ Proxy ที่พบบ่อย
- 5. Workshop 1: Virtual Proxy สำหรับรูปภาพ
- 6. Workshop 2: Protection Proxy สำหรับรายงาน
- 7. Static Proxy และ JDK Dynamic Proxy
- 8. Workshop 3: จับเวลาการเรียกด้วย Dynamic Proxy
- 9. Proxy ต่างจาก Decorator และ Adapter อย่างไร
- 10. ข้อดีและข้อแลกเปลี่ยน
- 11. ข้อผิดพลาดที่ควรสังเกต
- 12. แบบฝึกหัดต่อยอด
- 13. เกณฑ์ตรวจความเข้าใจ
- เอกสารอ้างอิง

Proxy Pattern คือการใช้ออบเจ็กต์ตัวแทนเพื่อควบคุมการเข้าถึงออบเจ็กต์จริง โดยตัวแทนและออบเจ็กต์จริงมีสัญญาการใช้งานร่วมกัน
บทความนี้อธิบายแนวคิด โครงสร้าง และการนำ Proxy Pattern มาใช้ใน Java ผ่านสามตัวอย่าง ได้แก่ การโหลดรูปภาพเมื่อจำเป็น การตรวจสิทธิ์อ่านรายงาน และการสร้าง Dynamic Proxy สำหรับจับเวลาการเรียกเมธอด
พื้นฐานที่ควรมี: Class, Interface, Composition และ Exception ใน Java
สภาพแวดล้อมสำหรับทำตาม: JDK 17 ขึ้นไป ไม่ใช้ Maven, Gradle หรือไลบรารีเพิ่มเติม
#1. ปัญหาที่ Proxy Pattern ช่วยแก้
สมมติว่าโปรแกรมมีรายการรูปภาพจำนวนมาก หากสร้างออบเจ็กต์แต่ละรูปแล้วโหลดข้อมูลทันที โปรแกรมอาจใช้เวลาและหน่วยความจำกับรูปที่ผู้ใช้ยังไม่ได้เปิดดู
อีกกรณีหนึ่งคือระบบรายงาน ผู้ใช้ทุกคนอาจเรียกบริการผ่านเมธอดเดียวกัน แต่ผู้ใช้บางคนเท่านั้นที่ควรอ่านรายงานได้ หากใส่การตรวจสิทธิ์กระจายอยู่ทุกหน้าจอ การดูแลกฎการเข้าถึงจะยุ่งยากขึ้น
Proxy ช่วยเพิ่มจุดควบคุมระหว่างผู้เรียกกับออบเจ็กต์จริง เช่น สร้างออบเจ็กต์จริงเมื่อจำเป็น ตรวจสิทธิ์ก่อนส่งคำขอ หรือคืนค่าจากแคชเมื่อมีข้อมูลที่ใช้ได้อยู่แล้ว
#2. Proxy Pattern คืออะไร
Proxy อยู่ในกลุ่ม Structural Design Patterns ซึ่งเน้นการประกอบความสัมพันธ์ระหว่างคลาสและออบเจ็กต์
หัวใจของรูปแบบนี้มีสามข้อ:
- ผู้เรียกใช้งานผ่านสัญญาร่วม เช่น Java interface
- Proxy รับคำขอและตัดสินใจว่าจะจัดการอย่างไร
- Proxy ส่งคำขอไปยังออบเจ็กต์จริงเมื่อเหมาะสม หรือจัดการเองตามหน้าที่ของตัวแทน
เปรียบได้กับเจ้าหน้าที่หน้าห้องเอกสาร ผู้ขอเอกสารติดต่อเจ้าหน้าที่ ซึ่งตรวจเงื่อนไขก่อนอนุญาตให้เข้าถึงเอกสารภายใน
การมีอินเทอร์เฟซร่วมช่วยให้โค้ดผู้เรียกใช้ Proxy แทนออบเจ็กต์จริงได้ แต่ไม่ได้แปลว่าพฤติกรรมและต้นทุนจะเหมือนเดิมทุกด้าน เช่น Proxy อาจปฏิเสธการเข้าถึง หรืออาจมีเวลาในการโหลดครั้งแรก
#3. องค์ประกอบของรูปแบบ
| องค์ประกอบ | หน้าที่ | ตัวอย่างในบทความ |
|---|---|---|
| Subject | กำหนดสัญญาร่วมสำหรับผู้เรียก | Image |
| Real Subject | ทำงานจริง | RealImage |
| Proxy | ควบคุมการเข้าถึง Real Subject | ImageProxy |
| Client | เรียกใช้งานผ่าน Subject | เมธอด main() |
classDiagram
class Image {
<<interface>>
+display() void
}
class RealImage {
-String fileName
+display() void
}
class ImageProxy {
-String fileName
-RealImage realImage
+display() void
}
class Client
Image <|.. RealImage
Image <|.. ImageProxy
ImageProxy --> RealImage : creates on demand
Client --> Image : uses
ImageProxy เก็บรายละเอียดที่จำเป็นสำหรับสร้าง RealImage โดยอ้างอิงออบเจ็กต์จริงหลังจากสร้างแล้ว ส่วน Client ใช้เพียง Image
#4. ประเภทของ Proxy ที่พบบ่อย
| ประเภท | จุดประสงค์ | ตัวอย่าง |
|---|---|---|
| Virtual Proxy | เลื่อนการสร้างหรือโหลดออบเจ็กต์ที่มีต้นทุนสูง | โหลดรูปเมื่อเปิดดู |
| Protection Proxy | ควบคุมสิทธิ์การเข้าถึง | ตรวจสิทธิ์ก่อนอ่านรายงาน |
| Remote Proxy | เป็นตัวแทนของบริการที่อยู่ต่างกระบวนการหรือเครื่อง | ตัวแทนเรียกบริการผ่านเครือข่าย |
| Caching Proxy | ใช้ผลลัพธ์เดิมเมื่อเงื่อนไขของแคชยังถูกต้อง | คืนข้อมูลที่เคยร้องขอ |
ประเภทเหล่านี้อธิบาย เจตนาของการใช้งาน ส่วน Static Proxy และ Dynamic Proxy อธิบาย วิธีสร้างตัวแทน ดังนั้น Protection Proxy อาจเขียนเป็นคลาสเองหรือสร้างแบบ Dynamic Proxy ก็ได้
#5. Workshop 1: Virtual Proxy สำหรับรูปภาพ
#5.1 เป้าหมาย
สร้าง ImageProxy ที่ยังไม่สร้าง RealImage ใน constructor แต่สร้างเมื่อเรียก display() ครั้งแรก และใช้ออบเจ็กต์เดิมในการเรียกครั้งถัดไป
ตัวอย่างจำลองการโหลดและแสดงภาพด้วยข้อความใน console เพื่อให้เห็นจังหวะการทำงาน โดยไม่ต้องเตรียมไฟล์รูปภาพหรือส่วนติดต่อแบบกราฟิก
#5.2 โค้ดฉบับสมบูรณ์
บันทึกเป็นไฟล์ ProxyDemo.java
import java.util.Objects;
public class ProxyDemo {
interface Image {
void display();
}
static final class RealImage implements Image {
private final String fileName;
RealImage(String fileName) {
this.fileName = Objects.requireNonNull(fileName);
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + fileName);
}
@Override
public void display() {
System.out.println("Displaying image: " + fileName);
}
}
static final class ImageProxy implements Image {
private final String fileName;
private RealImage realImage;
ImageProxy(String fileName) {
this.fileName = Objects.requireNonNull(fileName);
}
@Override
public synchronized void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public static void main(String[] args) {
Image image = new ImageProxy("campus.png");
System.out.println("Proxy created; image not loaded yet.");
System.out.println("--- First display ---");
image.display();
System.out.println("--- Second display ---");
image.display();
}
}
#5.3 วิธีคอมไพล์และรัน
เปิด Terminal หรือ Command Prompt ในโฟลเดอร์ที่บันทึกไฟล์ แล้วใช้คำสั่ง:
javac -encoding UTF-8 ProxyDemo.java
java ProxyDemo
ผลลัพธ์:
Proxy created; image not loaded yet.
--- First display ---
Loading image: campus.png
Displaying image: campus.png
--- Second display ---
Displaying image: campus.png
#5.4 อธิบายจุดสำคัญ
new ImageProxy(...)เก็บชื่อไฟล์อย่างเดียว จึงยังไม่เกิดข้อความLoading image- Client ประกาศตัวแปรเป็น
Imageและเรียกdisplay()ตามสัญญาร่วม - การเรียกครั้งแรกพบว่า
realImage == nullจึงสร้างออบเจ็กต์จริงและแสดงภาพ - การเรียกครั้งที่สองใช้
RealImageเดิม จึงมีเพียงข้อความแสดงภาพ - การนำออบเจ็กต์เดิมกลับมาใช้เกิดภายใน Proxy หนึ่งตัว หากสร้าง Proxy สองตัวด้วยชื่อไฟล์เดียวกัน ตัวอย่างนี้จะยังสร้าง Real Subject แยกกัน
#5.5 ลำดับการทำงาน
sequenceDiagram
participant C as Client
participant P as ImageProxy
participant R as RealImage
C->>P: display() ครั้งแรก
Note over P: ยังไม่มี RealImage
P->>R: สร้างและโหลดภาพ
P->>R: display()
R-->>P: ทำงานเสร็จ
P-->>C: ทำงานเสร็จ
C->>P: display() ครั้งที่สอง
Note over P: ใช้ RealImage เดิม
P->>R: display()
R-->>P: ทำงานเสร็จ
P-->>C: ทำงานเสร็จ
แต่ละการเรียก display() แสดงข้อความผ่าน console แล้วจบเมธอด โดยไม่มีค่าคืนกลับ เพราะสัญญากำหนดชนิดผลลัพธ์เป็น void
#5.6 เหตุผลที่ใช้ synchronized
หากหลาย thread เรียก Proxy ตัวเดียวกันพร้อมกัน ทั้งสอง thread อาจเห็น realImage == null และสร้างออบเจ็กต์จริงซ้ำได้ synchronized ในตัวอย่างจึงป้องกันการแข่งขันระหว่างการสร้างและเรียกใช้งานผ่าน Proxy ตัวนี้
วิธีนี้ล็อกครอบคลุมทั้ง display() จึงทำให้การเรียก Proxy เดียวกันเป็นลำดับ เหมาะกับตัวอย่างที่ต้องการความชัดเจน หากงานจริงใช้เวลาแสดงผลนาน ควรออกแบบการล็อกและความปลอดภัยของ Real Subject ให้เหมาะกับลักษณะการทำงาน
Lazy Loading ช่วยเลื่อนต้นทุนการโหลดไปจนถึงเวลาที่ต้องใช้ ไม่ได้ลดต้นทุนการโหลดภาพนั้นโดยอัตโนมัติ และผู้ใช้อาจรับรู้ความหน่วงในการเปิดภาพครั้งแรก
#6. Workshop 2: Protection Proxy สำหรับรายงาน
ตัวอย่างนี้ให้ Proxy ตรวจสิทธิ์ทุกครั้งก่อนส่งคำขอไปยังบริการรายงาน โดยแยกกฎการอนุญาตออกมาเป็น Predicate<String>
บันทึกเป็นไฟล์ ProtectionProxyDemo.java
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
public class ProtectionProxyDemo {
interface ReportService {
String readReport();
}
static final class RealReportService implements ReportService {
@Override
public String readReport() {
return "Report: SE-2026";
}
}
static final class ReportProxy implements ReportService {
private final ReportService target;
private final String userId;
private final Predicate<String> canRead;
ReportProxy(ReportService target, String userId,
Predicate<String> canRead) {
this.target = Objects.requireNonNull(target);
this.userId = Objects.requireNonNull(userId);
this.canRead = Objects.requireNonNull(canRead);
}
@Override
public String readReport() {
if (!canRead.test(userId)) {
throw new SecurityException("Access denied: " + userId);
}
return target.readReport();
}
}
public static void main(String[] args) {
Set<String> allowedReaders = Set.of("teacher");
ReportService realService = new RealReportService();
ReportService teacherService = new ReportProxy(
realService, "teacher", allowedReaders::contains);
System.out.println(teacherService.readReport());
ReportService guestService = new ReportProxy(
realService, "guest", allowedReaders::contains);
try {
System.out.println(guestService.readReport());
} catch (SecurityException e) {
System.out.println(e.getMessage());
}
}
}
รันด้วย:
javac -encoding UTF-8 ProtectionProxyDemo.java
java ProtectionProxyDemo
ผลลัพธ์:
Report: SE-2026
Access denied: guest
เมื่อ canRead คืนค่า false Proxy จะโยน exception ก่อนเรียก target.readReport() จึงไม่ส่งคำขอที่ถูกปฏิเสธไปยังบริการจริง
ในการนำไปใช้กับระบบจริง userId ต้องมาจากบริบทผู้ใช้ที่ผ่านการยืนยันตัวตนแล้ว และกฎการอนุญาตต้องมาจากแหล่งที่ระบบเชื่อถือได้ ตัวอย่างกำหนดค่าตรงใน main() เพื่อสาธิตรูปแบบเท่านั้น นอกจากนี้ โค้ดประกอบระบบควรส่งต่อเฉพาะ Proxy ให้ส่วนที่ต้องถูกควบคุม หากส่วนนั้นได้รับ realService โดยตรง ก็สามารถเรียกข้าม Proxy ได้
#7. Static Proxy และ JDK Dynamic Proxy
ตัวอย่างสองชุดแรกเป็น Static Proxy เพราะเราเขียนคลาส Proxy และเมธอดที่รับคำขอด้วยตนเอง ทำให้ติดตามการทำงานง่าย และกำหนดพฤติกรรมเฉพาะแต่ละเมธอดได้ชัดเจน
Java ยังมี JDK Dynamic Proxy ผ่าน java.lang.reflect.Proxy ซึ่งสร้างคลาสที่ implement อินเทอร์เฟซที่กำหนดขณะรัน และส่งการเรียกเมธอดไปยัง InvocationHandler [1][2]
| ประเด็น | Static Proxy | JDK Dynamic Proxy |
|---|---|---|
| วิธีสร้าง | เขียนคลาสเอง | สร้างคลาสขณะรันผ่าน API |
| การควบคุมเมธอด | เขียนโค้ดแต่ละเมธอดโดยตรง | ประมวลผลผ่าน handler |
| กรณีเหมาะสม | เงื่อนไขเฉพาะบริการหรือมีเมธอดไม่มาก | พฤติกรรมร่วมหลายเมธอด เช่น จับเวลาหรือบันทึกการเรียก |
| ขอบเขตของตัวอย่าง | ใช้ interface ร่วมกับ Real Subject | API รับชนิด interface เพื่อสร้างตัวแทน |
| สิ่งที่ต้องดูแล | โค้ด forwarding เมื่อมีเมธอดเพิ่ม | Reflection, exception และเมธอดจาก Object |
JDK Dynamic Proxy ไม่ได้สร้าง subclass ของคลาสธุรกิจเป้าหมาย และไม่สามารถส่งคลาสธรรมดาแทน interface ในรายการอินเทอร์เฟซได้ อย่างไรก็ตาม ตัวออบเจ็กต์เป้าหมายอาจเป็นคลาส final ได้ หากเราเรียกผ่านอินเทอร์เฟซที่มัน implement
#8. Workshop 3: จับเวลาการเรียกด้วย Dynamic Proxy
บันทึกเป็นไฟล์ DynamicProxyDemo.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Locale;
public class DynamicProxyDemo {
public interface GreetingService {
String greet(String name);
}
public static final class RealGreetingService
implements GreetingService {
@Override
public String greet(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required");
}
return "Hello, " + name;
}
}
public static void main(String[] args) {
GreetingService target = new RealGreetingService();
InvocationHandler handler = (proxy, method, methodArgs) -> {
if (method.getDeclaringClass() == Object.class) {
return switch (method.getName()) {
case "equals" -> proxy == methodArgs[0];
case "hashCode" -> System.identityHashCode(proxy);
case "toString" -> "TimingProxy[GreetingService]";
default -> throw new UnsupportedOperationException(
method.getName());
};
}
long started = System.nanoTime();
try {
return method.invoke(target, methodArgs);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
double elapsedMs =
(System.nanoTime() - started) / 1_000_000.0;
System.out.printf(Locale.ROOT, "[Timing] %s: %.3f ms%n",
method.getName(), elapsedMs);
}
};
GreetingService service = (GreetingService) Proxy.newProxyInstance(
GreetingService.class.getClassLoader(),
new Class<?>[] { GreetingService.class },
handler);
System.out.println(service.greet("Java"));
System.out.println(service);
System.out.println("Self equality: " + service.equals(service));
}
}
รันด้วย:
javac -encoding UTF-8 DynamicProxyDemo.java
java DynamicProxyDemo
รูปแบบผลลัพธ์ด้านล่างเป็นเพียงตัวอย่าง ค่าเวลาจะแตกต่างกันในแต่ละรอบ:
[Timing] greet: 0.123 ms
Hello, Java
TimingProxy[GreetingService]
Self equality: true
#สิ่งที่เกิดขึ้นใน handler
- เมื่อเรียก
service.greet("Java")JDK ส่งรายละเอียดเมธอดและอาร์กิวเมนต์เข้ามายัง handler [1][2] - handler บันทึกเวลาเริ่มต้น แล้วใช้
method.invoke(target, methodArgs)เรียกออบเจ็กต์จริง - ถ้าเมธอดเป้าหมายโยน exception การเรียกผ่าน reflection อาจห่อ exception ไว้ใน
InvocationTargetExceptionจึงดึงgetCause()เพื่อส่งสาเหตุเดิมออกไป [3] finallyบันทึกเวลาที่ผ่านไปทั้งกรณีสำเร็จและกรณีเกิด exception- JDK ส่งการเรียก
equals(),hashCode()และtoString()มายัง handler ด้วย ตัวอย่างจึงกำหนดพฤติกรรมเหล่านี้แยกไว้ โดยใช้ identity สำหรับ equality ของตัว Proxy [1]
หาก handler ส่ง checked exception ที่เมธอดในอินเทอร์เฟซไม่ประกาศรองรับ ผู้เรียกอาจได้รับ UndeclaredThrowableException จึงต้องออกแบบสัญญา exception ให้สอดคล้องกัน [2]
การจับเวลาด้วยวิธีนี้รวมค่าใช้จ่ายของ reflection บางส่วนและได้รับผลจากการ warm-up ของ JVM เหมาะสำหรับสังเกตการเรียกในตัวอย่าง ไม่ควรใช้ผลครั้งเดียวเป็นข้อสรุปเพื่อเปรียบเทียบประสิทธิภาพของอัลกอริทึม
#9. Proxy ต่างจาก Decorator และ Adapter อย่างไร
| รูปแบบ | เจตนาหลัก | ความสัมพันธ์ของสัญญา | ตัวอย่าง |
|---|---|---|---|
| Proxy | ควบคุมการเข้าถึงหรือตัวแทนของออบเจ็กต์ | มักมีสัญญาร่วมกับออบเจ็กต์จริง | โหลดภาพเมื่อจำเป็น ตรวจสิทธิ์ |
| Decorator | เพิ่มหน้าที่หรือความสามารถให้สิ่งที่ห่ออยู่ | คงสัญญาร่วมเพื่อประกอบความสามารถ | เพิ่มการเข้ารหัสหรือบีบอัดข้อมูล |
| Adapter | แปลงสัญญาของสิ่งเดิมให้เข้ากับสิ่งที่ผู้เรียกต้องการ | เชื่อมสัญญาที่ต่างกัน | แปลงบริการเก่าให้ใช้กับ interface ใหม่ |
Proxy และ Decorator อาจมีโครงสร้างคล้ายกันมาก เพราะต่างก็สามารถ implement อินเทอร์เฟซเดียวกับสิ่งที่ห่ออยู่และส่งต่อคำขอ การพิจารณาจึงต้องดูเจตนาของการออกแบบด้วย เช่น การจับเวลาอาจเขียนด้วยโครงสร้างคล้าย Decorator หรือด้วย Dynamic Proxy ก็ได้
#10. ข้อดีและข้อแลกเปลี่ยน
#ข้อดี
- รวมกฎการเข้าถึงไว้ในออบเจ็กต์เฉพาะหน้าที่
- เลื่อนการสร้างทรัพยากรไปจนกว่าจะจำเป็น
- ช่วยให้ Client พึ่งพา interface แทนรายละเอียดของ Real Subject
- เพิ่มการควบคุมโดยไม่ต้องแก้การทำงานหลักของ Real Subject ในหลายกรณี
#ข้อแลกเปลี่ยน
- เพิ่มชั้นการเรียกและจำนวนองค์ประกอบที่ต้องดูแล
- Lazy Loading ทำให้การเรียกครั้งแรกอาจช้ากว่าครั้งถัดไป
- แคชต้องมีนโยบายอายุข้อมูล การล้างข้อมูล และ key ที่ถูกต้อง
- การใช้หลาย thread ต้องออกแบบทั้งการสร้างและการเข้าถึง Real Subject
- Remote Proxy ต้องจัดการความล่าช้า timeout และความล้มเหลวของเครือข่ายเพิ่มเติม เพราะมี interface เหมือนการเรียกในเครื่องไม่ได้ทำให้เครือข่ายมีพฤติกรรมเหมือนการเรียกในเครื่อง
#11. ข้อผิดพลาดที่ควรสังเกต
#สร้าง Real Subject เร็วเกินไป
หากเป้าหมายคือ Virtual Proxy แต่ constructor ของ Proxy เรียก new RealImage(...) ทันที การเลื่อนการโหลดก็จะไม่เกิดขึ้น ควรเก็บข้อมูลสำหรับสร้างและสร้างเมื่อถึงจุดที่ต้องใช้งาน
#เรียกข้าม Proxy
Protection Proxy ตรวจสิทธิ์ได้เฉพาะคำขอที่วิ่งผ่านมัน การแจก reference ของ Real Subject ให้ Client โดยตรงอาจทำให้การควบคุมถูกข้ามได้
#คิดว่ามี Proxy แล้วจะมีแคชหรือทำงานข้ามเครือข่ายให้เอง
รูปแบบ Proxy ไม่ได้ติดตั้งความสามารถเหล่านี้โดยอัตโนมัติ ผู้พัฒนาต้องเขียนหรือเลือกกลไกที่รองรับพฤติกรรมที่ต้องการ
#ใช้ this ภายใน Real Subject แล้วคาดว่าจะผ่าน Proxy
หาก Real Subject เรียกอีกเมธอดหนึ่งของตัวเองผ่าน this การเรียกนั้นจะเกิดบนตัว Real Subject โดยตรง ไม่ได้ย้อนผ่าน Proxy ภายนอก จึงไม่ถูก handler ของ Proxy ตัวอย่างนี้ดักซ้ำ
#เชื่อว่า Caching Proxy ใช้ key จากอาร์กิวเมนต์อย่างเดียวได้เสมอ
ข้อมูลที่ขึ้นอยู่กับผู้ใช้ สิทธิ์ tenant หรือภาษา อาจต้องรวมบริบทดังกล่าวไว้ใน key และตรวจสิทธิ์ก่อนคืนผลลัพธ์ด้วย ควรกำหนดนโยบายตามสัญญาของบริการ
#12. แบบฝึกหัดต่อยอด
- เพิ่มการเรียก
image.display()เป็นสามครั้ง แล้วอธิบายว่าทำไมLoading imageยังเกิดครั้งเดียว - สร้าง
ImageProxyสองตัวด้วยชื่อไฟล์เดียวกัน แล้วเปรียบเทียบจำนวนครั้งที่โหลด - เปลี่ยนกฎ
canReadให้ตรวจจากชุดสิทธิ์ที่แก้ไขได้ แล้วทดลองถอนสิทธิ์หลังอ่านรายงานสำเร็จครั้งแรก - เรียก
service.greet("")และจับIllegalArgumentExceptionเพื่อสังเกตว่า handler ส่งสาเหตุเดิมออกมาและยังพิมพ์เวลา - ออกแบบ Caching Proxy สำหรับบริการค้นหาสินค้า โดยระบุ key อายุแคช และเงื่อนไขล้างข้อมูลก่อนเขียนโค้ด
#13. เกณฑ์ตรวจความเข้าใจ
เมื่อทำตัวอย่างเสร็จ ควรอธิบายได้ว่า Client อ้างอิงสัญญาใด Proxy ควบคุมอะไร และออบเจ็กต์จริงถูกเรียกเมื่อใด โดยแยกหน้าที่ของตัวแทนออกจากงานธุรกิจหลักอย่างชัดเจน
สำหรับ Virtual Proxy ผลที่ต้องสังเกตได้คือยังไม่โหลดรูปขณะสร้าง Proxy โหลดครั้งเดียวเมื่อเรียกครั้งแรก และยังแสดงรูปได้ทุกครั้งหลังจากนั้น ส่วน Protection Proxy ต้องไม่เรียกบริการจริงเมื่อผู้ใช้ไม่ได้รับอนุญาต
#เอกสารอ้างอิง
ตัวอย่าง Java ทั้งสามชุดเขียนขึ้นเพื่ออธิบายรูปแบบนี้โดยเฉพาะ ไม่ได้คัดลอกจากเอกสาร API
- Oracle, Proxy — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Proxy.html
- Oracle, InvocationHandler — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/InvocationHandler.html
- Oracle, InvocationTargetException — Java SE 21 API: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/InvocationTargetException.html
อ้างอิง API รุ่น 21 เพื่ออธิบายกลไกของ JDK Dynamic Proxy ส่วนโค้ดในบทความใช้คุณสมบัติที่รองรับใน Java 17