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

Chain of Responsibility

Chain of Responsibility ให้คุณส่ง request ผ่านลำดับของ handler แต่ละ handler จะจัดการ request เองหรือส่งต่อไปยังตัวถัดไป ดังนั้นผู้ส่งจึงไม่จำเป็นต้องรู้เลยว่า handler ตัวใดจะเป็นผู้ลงมือทำงานในที่สุด

ลองนึกถึงการตรวจสอบ request ที่เข้ามาก่อนที่แอปพลิเคชันของคุณจะลงมือทำงานกับ request นั้น คุณอาจตรวจว่าฟิลด์ที่จำเป็นมีครบ, payload ไม่ใหญ่เกินไป, ผู้เรียกได้รับการยืนยันตัวตน, และมีสิทธิ์เพียงพอ การยัดทั้งหมดนั้นไว้ในฟังก์ชันยักษ์เพียงตัวเดียวจะกลายเป็นเงื่อนไขซ้อนกันที่พันกันยุ่ง และการสลับลำดับหรือลบการตรวจสอบสักข้อหมายถึงการผ่าตัดบล็อกเดียวนั้น

Chain of Responsibility เปลี่ยนการตรวจสอบแต่ละข้อให้กลายเป็น handler เล็ก ๆ ของตัวเองที่มีความรับผิดชอบเดียว handler ถูกเชื่อมต่อกันตามลำดับ request เข้าสู่หัวแถวของ chain และเดินทางไปตามนั้นจนกว่าจะมี handler ปฏิเสธ หรือหลุดออกจากปลายแถวหลังผ่านการตรวจสอบทุกข้อแล้ว การเพิ่ม, ลบ, หรือสลับลำดับการตรวจสอบจึงกลายเป็นเรื่องของการเชื่อม handler ใหม่ ไม่ใช่การเขียนตรรกะใหม่ และ code ที่ส่ง request เข้ามาก็ยังคงไม่รับรู้เลยว่ามี handler อยู่กี่ตัว

classDiagram
  class Handler {
    <<interface>>
    +setNext(h Handler) Handler
    +handle(request) Result
  }
  class BaseHandler {
    -next Handler
    +setNext(h Handler) Handler
    +handle(request) Result
  }
  class ConcreteHandlerA {
    +handle(request) Result
  }
  class ConcreteHandlerB {
    +handle(request) Result
  }
  Handler <|.. BaseHandler
  BaseHandler <|-- ConcreteHandlerA
  BaseHandler <|-- ConcreteHandlerB
  BaseHandler --> Handler : next
แต่ละ handler ถือ reference ไปยังตัวถัดไปและอาจส่งต่อ request
  • Handler — interface ที่ทุก link ใช้ร่วมกัน ประกอบด้วยวิธีกำหนด handler ตัวถัดไปและเมท็อดที่ประมวลผล request
  • BaseHandler — base ที่ใช้ร่วมกันแบบไม่บังคับ ทำหน้าที่เก็บ link ตัวถัดไปและส่งต่อโดยปริยาย เพื่อให้ concrete handler override เฉพาะส่วนที่ตนสนใจเท่านั้น
  • ConcreteHandler — การตรวจสอบหรือขั้นตอนเฉพาะ ทำหน้าที่จัดการ request ให้เสร็จเองหรือมอบหมายไปยัง handler ตัวถัดไปในแถว
  • Client — สร้าง chain และส่ง request ไปยังหัวแถว ไม่ใช่ไปยัง handler ตัวใดตัวหนึ่งโดยเฉพาะ

chain การตรวจสอบเล็ก ๆ สำหรับ request สมัครสมาชิก แต่ละ handler ตรวจสอบ request แล้วรายงานปัญหาหรือส่งต่อไปตามแถว ผลลัพธ์ที่ว่างเปล่าหมายความว่าทุก handler อนุมัติแล้ว

interface SignUp {
email: string;
password: string;
}
abstract class Validator {
private next: Validator | null = null;
setNext(next: Validator): Validator {
this.next = next;
return next;
}
validate(req: SignUp): string | null {
const error = this.check(req);
if (error !== null) return error;
return this.next ? this.next.validate(req) : null;
}
protected abstract check(req: SignUp): string | null;
}
class EmailValidator extends Validator {
protected check(req: SignUp): string | null {
return req.email.includes('@') ? null : 'email is invalid';
}
}
class PasswordValidator extends Validator {
protected check(req: SignUp): string | null {
return req.password.length >= 8 ? null : 'password too short';
}
}
const chain = new EmailValidator();
chain.setNext(new PasswordValidator());
console.log(chain.validate({ email: '[email protected]', password: 'secret12' })); // null
console.log(chain.validate({ email: 'nope', password: 'secret12' })); // email is invalid
  • ข้อดี: แยกผู้ส่ง request ออกจาก handler ตัวที่ลงเอยด้วยการประมวลผล request นั้น
  • ข้อดี: แต่ละ handler มีงานเดียว และคุณสลับลำดับหรือขยายไปป์ไลน์ได้ด้วยการเชื่อมใหม่ ไม่ใช่การเขียนใหม่
  • ข้อเสีย: request อาจหลุดออกจากปลายแถวโดยไม่ถูกจัดการ คุณต้องตัดสินใจว่ายอมให้เกิดเหตุการณ์นั้นได้หรือไม่
  • ข้อเสีย: การดีบักยากขึ้นเพราะพฤติกรรมกระจายอยู่ทั่ว handler เล็ก ๆ จำนวนมากและขึ้นอยู่กับลำดับของ handler
  • Command มักเดินทางในฐานะ object request ที่ chain ประมวลผล
  • Composite และ Chain of Responsibility ต่างก็สร้างโครงสร้างแบบเชื่อมโยงกัน แต่ composite เป็นต้นไม้ที่คุณดำเนินการกับทั้งโครงสร้าง ขณะที่ chain เป็นแถวที่ request เดินผ่านไป
Chain of ResponsibilityCommandObserver
ใครจัดการ requesthandler แรกในสาย ที่รับได้command object เดี่ยวobserver ทุกตัว (broadcast)
ทิศทางเส้นตรง (chain)caller → command → receiver1 subject → N observer
skip handlerได้ไม่มีแนวคิดนี้ไม่ได้ (notify ทุกตัว)
ตัวอย่างmiddleware, auth, validation chainbutton, HTTP request, undoEventEmitter, DOM events
handler แต่ละตัวใน Chain of Responsibility ตัดสินใจเรื่องอะไร?
ทำไมผู้ส่งจึงแยกขาดจากผู้รับใน pattern นี้?
อะไรคือความเสี่ยงของการใช้ chain?