- 1. Chain of Responsibility คืออะไร
- 2. ปัญหาที่ต้องการแก้
- 3. ส่วนประกอบของ Pattern
- 4. กำหนดพฤติกรรมก่อนเขียนโค้ด
- 5. Workshop: สร้างระบบอนุมัติด้วย Java
- 6. อ่านโค้ดให้เห็นแนวคิด
- 7. ติดตามคำขอ 12,000 บาท
- 8. เพิ่มผู้อนุมัติระดับใหม่
- 9. สองลักษณะการใช้งานที่ควรแยกให้ชัด
- 10. ข้อดีและข้อแลกเปลี่ยน
- 11. เปรียบเทียบกับ Pattern ใกล้เคียง
- 12. กรณีทดสอบที่ควรตรวจ
- 13. ข้อผิดพลาดที่พบบ่อย
- 14. แบบฝึกหัด
- 15. ประเด็นสำคัญ
- แหล่งอ้างอิงและอ่านเพิ่มเติม
ออกแบบการส่งต่อคำขอให้ผู้รับผิดชอบแต่ละระดับ โดยให้ผู้ส่งคำขอรู้จักเพียงจุดเริ่มต้นของสายงาน
หมวดหมู่: Behavioral Design Pattern
#1. Chain of Responsibility คืออะไร
Chain of Responsibility เป็นรูปแบบการออกแบบที่เชื่อมออบเจ็กต์ผู้รับคำขอ หรือ Handler เป็นสาย แต่ละ Handler ตัดสินใจว่าจะจัดการคำขอนั้นเอง ส่งต่อให้ Handler ถัดไป หรือยุติการทำงานตามกติกาที่ออกแบบไว้
ผู้ส่งคำขอ หรือ Client เรียกผ่านอินเทอร์เฟซเดียว และไม่จำเป็นต้องรู้ว่าผู้รับผิดชอบตัวจริงเป็นออบเจ็กต์ใด ทำให้แยกส่วนที่ส่งคำขอออกจากส่วนที่กำหนดความรับผิดชอบได้
ตัวอย่างที่พบได้ เช่น การส่งเรื่องให้เจ้าหน้าที่ช่วยเหลือแต่ละระดับ การตรวจคำขอผ่านตัวกรอง และการอนุมัติรายการตามวงเงิน
หลักสำคัญ: กำหนดให้ชัดว่า Handler จะหยุดเมื่อใด ส่งต่อเมื่อใด และเมื่อคำขอไปถึงปลายสายโดยไม่มีผู้รับผิดชอบจะเกิดอะไรขึ้น
#2. ปัญหาที่ต้องการแก้
สมมติระบบอนุมัติค่าใช้จ่ายมีกติกาดังนี้
| ผู้อนุมัติ | วงเงินสูงสุดต่อรายการ |
|---|---|
| หัวหน้าทีม — TeamLead | 5,000 บาท |
| ผู้จัดการ — Manager | 20,000 บาท |
| ผู้อำนวยการ — Director | 100,000 บาท |
คำขอที่มียอดไม่เกินวงเงินของ Handler คนแรกที่รับได้ จะได้รับอนุมัติและจบการทำงาน หากยอดเกิน 100,000 บาท จะได้ผลลัพธ์ว่าไม่มีผู้มีอำนาจอนุมัติในสายนี้
วิธีเริ่มต้นอาจเขียนเงื่อนไขรวมไว้ในเมธอดเดียว
if (amount.compareTo(new BigDecimal("5000")) <= 0) {
// TeamLead approves
} else if (amount.compareTo(new BigDecimal("20000")) <= 0) {
// Manager approves
} else if (amount.compareTo(new BigDecimal("100000")) <= 0) {
// Director approves
} else {
// No eligible approver
}
โค้ดแบบนี้เหมาะกับกติกาที่เล็กและคงที่ แต่เมื่อแต่ละระดับเริ่มมีพฤติกรรมต่างกัน เช่น ตรวจหมวดค่าใช้จ่าย ตรวจงบประมาณ หรือเรียกบริการภายนอก เมธอดกลางจะรับผิดชอบหลายเรื่อง และต้องแก้ไขทุกครั้งที่เพิ่มผู้อนุมัติประเภทใหม่
Chain of Responsibility ช่วยแยกผู้รับผิดชอบออกเป็นออบเจ็กต์ และย้ายการกำหนดลำดับไปไว้ในจุดประกอบระบบ
หากมีเพียงตารางชื่อกับวงเงิน และทุกระดับทำงานเหมือนกันทั้งหมด การวนรายการกฎหรือใช้คลาสที่รับวงเงินเป็นพารามิเตอร์อาจเรียบง่ายกว่า ไม่จำเป็นต้องสร้างคลาสใหม่ให้ทุกแถวของตารางเสมอไป
#3. ส่วนประกอบของ Pattern
| บทบาท | หน้าที่ | ชื่อในตัวอย่าง |
|---|---|---|
| Request | ข้อมูลคำขอที่ถูกส่งเข้าไป | ExpenseRequest |
| Handler | สัญญากลางสำหรับรับคำขอ | ApprovalHandler |
| Base Handler | เก็บตัวถัดไปและรวมตรรกะการส่งต่อ | Approver |
| Concrete Handler | ผู้รับผิดชอบแต่ละประเภท | TeamLead, Manager, Director |
| Client | ส่งคำขอเข้าจุดเริ่มต้น | main() |
| Result | ผลการจัดการคำขอ | ApprovalResult |
Base Handler เป็นตัวช่วยลดโค้ดซ้ำ ไม่ใช่ข้อบังคับของ Pattern สามารถออกแบบด้วย interface และ composition โดยไม่ใช้คลาสฐานก็ได้
#แผนภาพโครงสร้าง
classDiagram
direction TB
class ApprovalHandler {
<<interface>>
+handle(request) ApprovalResult
}
class Approver {
<<abstract>>
-ApprovalHandler next
-BigDecimal limit
+handle(request) ApprovalResult
}
class TeamLead
class Manager
class Director
ApprovalHandler <|.. Approver
Approver <|-- TeamLead
Approver <|-- Manager
Approver <|-- Director
Approver --> ApprovalHandler : next
#4. กำหนดพฤติกรรมก่อนเขียนโค้ด
บทความนี้ใช้รูปแบบ พบผู้รับผิดชอบแล้วหยุด โดยมีสัญญาการทำงานดังนี้
- จำนวนเงินต้องมากกว่าศูนย์ และข้อมูลที่จำเป็นต้องไม่เป็น
null - เริ่มตรวจที่หัวหน้าทีม จากนั้นจึงเป็นผู้จัดการและผู้อำนวยการ
- ถ้ายอดน้อยกว่าหรือเท่ากับวงเงินของผู้รับคำขอ ให้คืนผลอนุมัติทันที
- ถ้าเกินวงเงิน ให้ส่งคำขอเดิมไปยังตัวถัดไป
- ถ้าไม่มีตัวถัดไป ให้คืนสถานะ
UNHANDLED
UNHANDLED หมายถึง ไม่มีผู้รับผิดชอบตามกติกาในสายนี้ ไม่ได้แปลว่าเป็นการปฏิเสธเชิงธุรกิจทุกกรณี ระบบจริงอาจส่งต่อคณะกรรมการ ให้เจ้าหน้าที่ตรวจ หรือบันทึกเป็นคำขอที่ต้องดำเนินการเพิ่มเติม
#ขั้นตอนการตัดสินใจของ Handler หนึ่งตัว
flowchart TD
A["รับคำขอ"] --> B{"ยอดไม่เกินวงเงิน?"}
B -->|ใช่| C["อนุมัติและหยุด"]
B -->|ไม่ใช่| D{"มีตัวถัดไป?"}
D -->|มี| E["ส่งต่อคำขอเดิม"]
D -->|ไม่มี| F["คืน UNHANDLED"]
#5. Workshop: สร้างระบบอนุมัติด้วย Java
#ขั้นที่ 1: ตรวจสอบเครื่องมือ
ติดตั้ง JDK 17 ขึ้นไป แล้วตรวจสอบด้วยคำสั่ง
java --version
javac --version
ตัวอย่างใช้ record สำหรับข้อมูลคำขอและผลลัพธ์ โดยไม่พึ่ง framework หรือ dependency ภายนอก
#ขั้นที่ 2: สร้างไฟล์ ChainOfResponsibilityDemo.java
โค้ดต่อไปนี้ครบในไฟล์เดียว คัดลอกไปคอมไพล์ได้ทันที
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
public class ChainOfResponsibilityDemo {
enum Status {
APPROVED,
UNHANDLED
}
record ExpenseRequest(String id, BigDecimal amount) {
ExpenseRequest {
Objects.requireNonNull(id, "id must not be null");
Objects.requireNonNull(amount, "amount must not be null");
if (id.isBlank()) {
throw new IllegalArgumentException("id must not be blank");
}
if (amount.signum() <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
}
}
record ApprovalResult(Status status, String handledBy) {
}
interface ApprovalHandler {
ApprovalResult handle(ExpenseRequest request);
}
abstract static class Approver implements ApprovalHandler {
private final String name;
private final BigDecimal limit;
private final ApprovalHandler next;
protected Approver(String name, String limit, ApprovalHandler next) {
this.name = name;
this.limit = new BigDecimal(limit);
this.next = next;
}
@Override
public final ApprovalResult handle(ExpenseRequest request) {
Objects.requireNonNull(request, "request must not be null");
if (request.amount().compareTo(limit) <= 0) {
return new ApprovalResult(Status.APPROVED, name);
}
if (next != null) {
return next.handle(request);
}
return new ApprovalResult(Status.UNHANDLED, "-");
}
}
static final class TeamLead extends Approver {
TeamLead(ApprovalHandler next) {
super("TeamLead", "5000", next);
}
}
static final class Manager extends Approver {
Manager(ApprovalHandler next) {
super("Manager", "20000", next);
}
}
static final class Director extends Approver {
Director(ApprovalHandler next) {
super("Director", "100000", next);
}
}
public static void main(String[] args) {
ApprovalHandler chain = new TeamLead(
new Manager(
new Director(null)));
List<ExpenseRequest> requests = List.of(
new ExpenseRequest("EXP-001", new BigDecimal("3000")),
new ExpenseRequest("EXP-002", new BigDecimal("12000")),
new ExpenseRequest("EXP-003", new BigDecimal("70000")),
new ExpenseRequest("EXP-004", new BigDecimal("150000"))
);
for (ExpenseRequest request : requests) {
ApprovalResult result = chain.handle(request);
System.out.printf("%s | %s THB | %s | %s%n",
request.id(),
request.amount().toPlainString(),
result.status(),
result.handledBy());
}
}
}
#ขั้นที่ 3: คอมไพล์และรัน
เปิด Terminal ในโฟลเดอร์ที่เก็บไฟล์ แล้วรัน
javac --release 17 ChainOfResponsibilityDemo.java
java ChainOfResponsibilityDemo
หรือใช้โหมดรันไฟล์ต้นฉบับโดยตรงของ JDK
java ChainOfResponsibilityDemo.java
ผลลัพธ์
EXP-001 | 3000 THB | APPROVED | TeamLead
EXP-002 | 12000 THB | APPROVED | Manager
EXP-003 | 70000 THB | APPROVED | Director
EXP-004 | 150000 THB | UNHANDLED | -
#6. อ่านโค้ดให้เห็นแนวคิด
#6.1 Client รู้จักเพียง Handler ตัวแรก
ApprovalResult result = chain.handle(request);
Client ไม่ต้องเลือกผู้อนุมัติด้วย if-else และไม่ต้องเรียก Manager หรือ Director เอง ผู้รับคำขอแต่ละตัวเป็นผู้ตัดสินใจส่งต่อ
#6.2 การเชื่อมสายอยู่ในจุดประกอบระบบ
ApprovalHandler chain = new TeamLead(
new Manager(
new Director(null)));
การเขียน constructor ซ้อนกันหมายถึงประกอบจากปลายสายกลับมาหาต้นสาย แต่คำขอจะเริ่มจาก TeamLead และเดินไปตาม next
ใช้ final กับตัวอ้างอิง next เพื่อไม่ให้เปลี่ยนสายระหว่างประมวลผล ตัวอย่างนี้ไม่เก็บข้อมูลคำขอไว้ใน Handler จึงไม่มีสถานะของคำขอค้างข้ามการเรียกใช้งาน
#6.3 จัดการแล้วต้องหยุด
if (request.amount().compareTo(limit) <= 0) {
return new ApprovalResult(Status.APPROVED, name);
}
คำสั่ง return ทำให้รายการหนึ่งได้รับอนุมัติโดยผู้รับผิดชอบเพียงคนเดียว หากอนุมัติแล้วส่งต่ออีก จะเป็นพฤติกรรมคนละแบบกับสัญญาของตัวอย่างนี้
#6.4 จัดการไม่ได้จึงส่งต่อ
if (next != null) {
return next.handle(request);
}
ส่งคำขอเดิมไปยังตัวถัดไป พร้อมส่งผลลัพธ์ย้อนกลับให้ Client โดยไม่เปลี่ยนความหมายระหว่างทาง
#6.5 ปลายสายต้องมีผลลัพธ์ชัดเจน
return new ApprovalResult(Status.UNHANDLED, "-");
การคืนสถานะชัดเจนช่วยให้ Client แยกคำขอที่อนุมัติแล้วออกจากคำขอที่ยังต้องดำเนินการต่อ แทนการคืน null แล้วให้ผู้เรียกเดาความหมาย
#6.6 เหตุใดใช้ BigDecimal
ตัวอย่างสร้างยอดเงินจากข้อความ เช่น new BigDecimal("12000.50") เพื่อเก็บค่าทศนิยมตามที่ระบุ และใช้ compareTo() สำหรับเปรียบเทียบค่าตัวเลข
ตามเอกสาร Java ค่า BigDecimal("2.0") และ BigDecimal("2.00") เปรียบเทียบด้วย compareTo() แล้วเท่ากัน แต่ equals() พิจารณา scale ด้วย [1] จึงควรเลือกวิธีเปรียบเทียบให้ตรงกับกติกา
ระบบเงินจริงยังต้องกำหนดสกุลเงิน จำนวนตำแหน่งทศนิยม และนโยบายการปัดเศษให้ชัดเจน โค้ดนี้สาธิตการตัดสินวงเงิน ไม่ได้คำนวณภาษีหรืออัตราแลกเปลี่ยน
#7. ติดตามคำขอ 12,000 บาท
| ลำดับ | ผู้รับคำขอ | การตัดสินใจ |
|---|---|---|
| 1 | TeamLead | 12,000 เกิน 5,000 จึงส่งต่อ |
| 2 | Manager | 12,000 ไม่เกิน 20,000 จึงอนุมัติ |
| 3 | Director | ไม่ถูกเรียก เพราะ Manager จบคำขอแล้ว |
ผลสุดท้ายคือ APPROVED โดย Manager
กรณีที่ลำดับเปลี่ยน: หากวาง Director ไว้ก่อน TeamLead รายการ 3,000 บาทจะถูก Director อนุมัติทันที เพราะอยู่ในวงเงิน ดังนั้นลำดับเป็นส่วนหนึ่งของกติกาธุรกิจ ต้องกำหนดและตรวจสอบอย่างตั้งใจ
#8. เพิ่มผู้อนุมัติระดับใหม่
สมมติเพิ่มรองผู้อำนวยการ ซึ่งอนุมัติได้ไม่เกิน 50,000 บาท ให้เพิ่มคลาสนี้ภายใน ChainOfResponsibilityDemo เช่นเดียวกับคลาสอื่น
static final class DeputyDirector extends Approver {
DeputyDirector(ApprovalHandler next) {
super("DeputyDirector", "50000", next);
}
}
จากนั้นเปลี่ยนจุดประกอบสายเป็น
ApprovalHandler chain = new TeamLead(
new Manager(
new DeputyDirector(
new Director(null))));
รายการ 35,000 บาทจะได้รับอนุมัติจาก DeputyDirector โดยไม่ต้องเปลี่ยนโค้ดที่ส่งคำขอหรือเมธอด handle() เดิม แต่ยังต้องแก้จุดประกอบสายให้รวมผู้อนุมัติใหม่
หากภายหลังแต่ละระดับมีกฎต่างกันมาก ควรแยกจุดตัดสินใจ เช่น canHandle(request) และจุดดำเนินการ เช่น approve(request) ให้ Concrete Handler กำหนดได้ แทนการขยายคลาสฐานด้วยเงื่อนไขเฉพาะทุกประเภท
#9. สองลักษณะการใช้งานที่ควรแยกให้ชัด
| ประเด็น | พบผู้รับผิดชอบแล้วหยุด | ตรวจเป็นลำดับแล้วส่งต่อ |
|---|---|---|
| เป้าหมาย | หาผู้รับผิดชอบที่เหมาะสมหนึ่งราย | ให้หลายขั้นตรวจหรือประมวลผลตามหน้าที่ |
| เมื่อทำงานของตัวเองสำเร็จ | คืนผลและหยุด | อาจเรียกขั้นถัดไป |
| เมื่อไม่ผ่านเงื่อนไข | อาจส่งต่อให้ผู้รับผิดชอบอื่น | อาจยุติสายทันที |
| ตัวอย่าง | อนุมัติตามวงเงิน, ส่งเรื่องเจ้าหน้าที่ | authentication, authorization, request filters |
ตัวอย่างอนุมัติในบทความเป็นแบบแรก ส่วน filter chain อาจให้หลาย filter ทำงานกับคำขอเดียว แต่แต่ละ filter ยังมีสิทธิ์หยุดสายตามกติกา
เอกสาร Spring Security อธิบายว่า Servlet Filter สามารถป้องกันไม่ให้ filter ถัดไปหรือ servlet ถูกเรียก หรือปรับ request/response แล้วส่งต่อได้ [2] ส่วน Jakarta FilterChain.doFilter() ใช้เรียก filter ถัดไป หรือทรัพยากรปลายสายเมื่อไม่มี filter ถัดไป [3]
ในเชิงการออกแบบ การส่งต่อและการหยุดเหล่านี้ใช้แนวคิดความรับผิดชอบเป็นสายได้ แต่ไม่ได้หมายความว่า filter chain ทุกระบบต้องมีโครงสร้างคลาสเหมือนตัวอย่างอนุมัตินี้
#10. ข้อดีและข้อแลกเปลี่ยน
| ประเด็น | ผลต่อการออกแบบ |
|---|---|
| ลดการผูกติด | Client พึ่งพา Handler interface แทนผู้รับผิดชอบแต่ละประเภท |
| แยกความรับผิดชอบ | กฎของแต่ละผู้รับคำขออยู่ในส่วนที่เกี่ยวข้อง |
| เพิ่มหรือจัดลำดับได้ | เปลี่ยนการประกอบสายได้โดยไม่แก้โค้ดผู้ส่งคำขอ |
| ติดตามเส้นทางยากขึ้น | ต้องรู้ว่าคำขอผ่าน Handler ใดและหยุดที่ใด |
| ไม่มีผู้รับประกันผล | คำขออาจถึงปลายสาย จึงต้องมี fallback ชัดเจน |
| ลำดับเปลี่ยนผลลัพธ์ | Handler ที่กฎครอบคลุมกว้างอาจรับคำขอทั้งหมดก่อนตัวอื่น |
| จำนวนออบเจ็กต์เพิ่ม | สำหรับกฎเล็กและคงที่ โค้ดอาจซับซ้อนเกินความจำเป็น |
สำหรับสายที่มี Handler จำนวน n ตัว เวลาการส่งต่อกรณีเลวร้ายสุดเป็น O(n) โดยยังไม่รวมต้นทุนงานของแต่ละ Handler ตัวอย่างนี้เรียกซ้อนผ่าน next.handle() จึงอาจใช้ call stack ตามจำนวน Handler ที่ผ่าน และต้องระวังหากสายยาวมาก
#11. เปรียบเทียบกับ Pattern ใกล้เคียง
| Pattern | คำถามหลักที่ Pattern ตอบ |
|---|---|
| Chain of Responsibility | ใครจะรับผิดชอบคำขอนี้ และจะส่งต่อหรือหยุดเมื่อใด? |
| Strategy | จะเลือกวิธีหรืออัลกอริทึมใดมาทำงาน? |
| Decorator | จะห่อออบเจ็กต์เพื่อเพิ่มพฤติกรรมอย่างไร? |
| Command | จะเก็บการเรียกงานเป็นออบเจ็กต์อย่างไร? |
| Observer | เมื่อมีเหตุการณ์ จะส่งการแจ้งเตือนไปยังผู้ติดตามใดบ้าง? |
Chain of Responsibility กับ Decorator อาจมีรูปร่างเป็นออบเจ็กต์ต่อกันคล้ายกัน แต่เจตนาต่างกัน: แบบแรกเน้นการเลือกผู้รับผิดชอบและการส่งต่อ ส่วนแบบหลังเน้นเพิ่มพฤติกรรมด้วยการห่อออบเจ็กต์
#12. กรณีทดสอบที่ควรตรวจ
เริ่มทดสอบที่ขอบเขตวงเงิน เพราะเงื่อนไข < กับ <= ให้ผลต่างกันอย่างมีนัยสำคัญ
| จำนวนเงินหรือข้อมูลเข้า | ผลที่คาดหวัง |
|---|---|
| 0.01 | APPROVED โดย TeamLead |
| 5,000 | APPROVED โดย TeamLead |
| 5,000.01 | APPROVED โดย Manager |
| 20,000 | APPROVED โดย Manager |
| 20,000.01 | APPROVED โดย Director |
| 100,000 | APPROVED โดย Director |
| 100,000.01 | UNHANDLED |
| 0 หรือค่าติดลบ | IllegalArgumentException ตอนสร้างคำขอ |
| รหัสว่าง | IllegalArgumentException ตอนสร้างคำขอ |
รหัสหรือจำนวนเงินเป็น null |
NullPointerException ตอนสร้างคำขอ |
chain.handle(null) |
NullPointerException |
นอกจากผลลัพธ์สุดท้าย ควรตรวจพฤติกรรม หยุดสาย ด้วย เช่น ส่ง 3,000 บาทเข้า TeamLead แล้วพิสูจน์ว่า Handler ถัดไปไม่ถูกเรียก โดยใช้ตัวทดแทนที่นับจำนวนการเรียกหรือโยนข้อผิดพลาดหากถูกเรียก
#13. ข้อผิดพลาดที่พบบ่อย
- ประกอบสายแล้วเรียกผิดจุด: ต้องเรียก Handler ตัวแรก มิฉะนั้นอาจข้ามกฎบางระดับ
- อนุมัติแล้วส่งต่ออีก: ทำให้หลายตัวจัดการรายการเดียวโดยไม่ตั้งใจ
- ลืมคืนผลจากตัวถัดไป: ใช้
return next.handle(request)เมื่อเมธอดมีผลลัพธ์ - ไม่มีนโยบายปลายสาย: กำหนดสถานะ ข้อผิดพลาด หรือ fallback ให้ผู้เรียกจัดการได้
- ต่อสายวนกลับ: หากใช้ setter เชื่อม Handler ต้องป้องกัน cycle ไม่เช่นนั้นอาจเรียกซ้ำจน stack เต็ม
- เก็บคำขอใน field ของ Handler: อาจทำให้ข้อมูลข้ามคำขอหรือเกิดปัญหาเมื่อใช้พร้อมกันหลายเธรด
- นำ exception ทุกชนิดไปตีความว่าให้ส่งต่อ: ต้องแยก “ไม่ใช่ผู้รับผิดชอบ” ออกจาก “ระบบทำงานล้มเหลว” เพราะมีความหมายต่างกัน
ในระบบจริงควรบันทึก request ID, ชื่อ Handler, เหตุผลที่ส่งต่อ และผลสุดท้าย เพื่ออธิบายเส้นทางของคำขอได้ การเพิ่มฐานข้อมูลหรือบริการภายนอกยังต้องออกแบบธุรกรรมและการป้องกันการอนุมัติซ้ำเพิ่มเติม
#14. แบบฝึกหัด
- เพิ่ม
DeputyDirectorแล้วทดลองยอด 35,000 บาท พร้อมอธิบายว่าทำไมต้องวางก่อน Director - สลับให้ Director เป็นตัวแรก แล้วเปรียบเทียบผลของรายการ 3,000 บาท
- เพิ่มสถานะ
MANUAL_REVIEWและออกแบบ Handler ปลายสายที่ส่งคำขอเข้าสู่การตรวจด้วยเจ้าหน้าที่ - เพิ่ม trace สำหรับแต่ละคำขอ โดยไม่เก็บ trace เป็น field ที่เปลี่ยนแปลงได้ของ Handler ที่ใช้ร่วมกัน
- ออกแบบสายตรวจ API ซึ่งตรวจข้อมูล การยืนยันตัวตน และสิทธิ์การเข้าถึง แล้วระบุว่าขั้นใดต้องส่งต่อและขั้นใดต้องหยุด
#15. ประเด็นสำคัญ
Chain of Responsibility ทำให้ผู้ส่งคำขอเรียกผ่านสัญญากลาง ขณะที่ Handler แต่ละตัวเป็นผู้ตัดสินใจว่าจะรับผิดชอบหรือส่งต่อ ความถูกต้องของการออกแบบขึ้นอยู่กับ กติกาการหยุด ลำดับของสาย และผลลัพธ์เมื่อไม่มีผู้รับผิดชอบ
สำหรับตัวอย่าง Java นี้ ApprovalHandler เป็นสัญญากลาง Approver จัดการตรรกะร่วม และผู้อนุมัติแต่ละระดับกำหนดวงเงินของตนเอง ผู้เรียกส่งคำขอครั้งเดียวและได้รับผลลัพธ์ที่ระบุผู้อนุมัติหรือสถานะ UNHANDLED อย่างชัดเจน
#แหล่งอ้างอิงและอ่านเพิ่มเติม
- Oracle Java SE 17 — BigDecimal — การเก็บทศนิยมและความต่างของ
compareTo()กับequals() - Spring Security — Architecture — บทบาทของ Servlet Filter และการส่งต่อหรือยุติสาย
- Jakarta EE Web Profile API — FilterChain — สัญญาของ
doFilter()