Trang chủHướng dẫnTìm hiểu mẫu thiết kế Chain of Responsibility trong Java
Java

Tìm hiểu mẫu thiết kế Chain of Responsibility trong Java

CyStack blog 6 phút để đọc
CyStack blog08/07/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 6 minutes

Mẫu chuỗi trách nhiệm (Chain of Responsibility) là một trong các mẫu thiết kế hành vi (behavioral design pattern).

Chain of Responsibility trong Java

Nó được sử dụng để đạt được tính loose coupling (liên kết lỏng giữa các thành phần) trong thiết kế phần mềm. Cụ thể, mỗi request từ client sẽ được chuyển qua một chuỗi các đối tượng để xử lý. Từng đối tượng trong chuỗi sẽ tự quyết định ai sẽ là người xử lý request và liệu request đó có cần được chuyển đến đối tượng tiếp theo trong chuỗi hay không.

Ví dụ dùng mẫu chuỗi trách nhiệm trong JDK

Hãy cùng xem một ví dụ trước khi tiến hành triển khai thử mẫu thiết kế này. Như bạn đã biết, trong Java ta có thể có nhiều khối catch trong một khối try-catch. Ở đây, mỗi khối catch giống như một “bộ xử lý” cho một exception (lỗi) cụ thể. Khi có bất kỳ exception nào xảy ra trong khối try, nó sẽ được gửi đến khối catch đầu tiên để xử lý.

Nếu khối catch đó không xử lý được, nó sẽ chuyển request đến đối tượng tiếp theo trong chuỗi, tức là khối catch kế tiếp. Nếu ngay cả đến khối catch cuối cùng cũng không thể xử lý được nó, exception sẽ bị chuyển ra ngoài chuỗi và đến chương trình đã gọi nó.

Tình huống ví dụ

Một ví dụ điển hình của mẫu thiết kế Chain of Responsibility là máy rút tiền ATM. Người dùng nhập số tiền cần rút, và máy sẽ trả tiền bằng các mệnh giá đã được định sẵn như $50, $20, $10, v.v. Nếu người dùng nhập một số tiền không phải là bội số của 10, máy sẽ báo lỗi.

Ta sẽ sử dụng mẫu Chain of Responsibility để hiện thực giải pháp này. Chuỗi xử lý sẽ hoạt động theo thứ tự như trong hình bên dưới.

 

Sơ đồ ví dụ mẫu Chain of responsibilityLưu ý rằng ta hoàn toàn có thể hiện thực hóa giải pháp này trong một chương trình duy nhất, nhưng khi đó độ phức tạp sẽ tăng lên và sẽ bị tight coupling (các thành phần ràng buột quá chặt vào nhau). Do đó, ta sẽ tạo ra một chuỗi các hệ thống trả tiền để xử lý các tờ tiền mệnh giá $50, $20, và $10.

Các class và interface cơ sở

Ta có thể tạo một class để lưu trữ số tiền cần rút và class này sẽ được các triển khai trong chuỗi sử dụng.

File Currency.java:

package com.journaldev.design.chainofresponsibility;

public class Currency {

	private int amount;
	
	public Currency(int amt){
		this.amount=amt;
	}
	
	public int getAmount(){
		return this.amount;
	}
}

Interface cơ sở nên có một phương thức để xác định bước xử lý tiếp theo trong chuỗi và một phương thức khác để xử lý request. Interface DispenseChain của chúng ta sẽ trông như bên dưới.

File DispenseChain.java:

package com.journaldev.design.chainofresponsibility;

public interface DispenseChain {

	void setNextChain(DispenseChain nextChain);
	
	void dispense(Currency cur);
}

Các triển khai của chuỗi

Ta cần tạo ra các class xử lý (processor) khác nhau để triển khai interface DispenseChain và các phưng thức trả tiền. Vì hệ thống của chúng ta xử lý ba loại mệnh giá là $50, $20, và $10, ta sẽ tạo ba class triển khai cụ thể.

File Dollar50Dispenser.java:

package com.journaldev.design.chainofresponsibility;

public class Dollar50Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 50){
			int num = cur.getAmount()/50;
			int remainder = cur.getAmount() % 50;
			System.out.println("Dispensing "+num+" 50$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

File Dollar20Dispenser.java:

public class Dollar20Dispenser implements DispenseChain{

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 20){
			int num = cur.getAmount()/20;
			int remainder = cur.getAmount() % 20;
			System.out.println("Dispensing "+num+" 20$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

File Dollar10Dispenser.java:

package com.journaldev.design.chainofresponsibility;

public class Dollar10Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain=nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if(cur.getAmount() >= 10){
			int num = cur.getAmount()/10;
			int remainder = cur.getAmount() % 10;
			System.out.println("Dispensing "+num+" 10$ note");
			if(remainder !=0) this.chain.dispense(new Currency(remainder));
		}else{
			this.chain.dispense(cur);
		}
	}

}

Điểm quan trọng cần lưu ý ở đây là triển khai của phương thức trả tiền. Bạn sẽ nhận thấy rằng mỗi triển khai đều cố gắng xử lý request, và tùy thuộc vào số tiền, nó có thể xử lý một phần hoặc toàn bộ. Nếu một đối tượng trong chuỗi không thể xử lý hoàn toàn, nó sẽ chuyển request (với số tiền còn lại) cho bộ xử lý kế tiếp. Nếu một bộ xử lý không thể xử lý được gì, nó chỉ đơn giản chuyển tiếp nguyên vẹn request đó cho đối tượng tiếp theo.

Tạo chuỗi xử lý

Đây là một bước rất quan trọng và ta phải xây dựng chuỗi một cách cẩn thận, nếu không một processor có thể sẽ không bao giờ nhận được request nào. Ví dụ, trong cách hiện thực của ta, nếu ta để bộ xử lý tờ 10$ đứng trước nơi xử lý tờ 20$, thì request sẽ không bao giờ được chuyển tiếp đến bộ xử lý thứ hai, và chuỗi xử lý sẽ trở nên vô dụng.

Dưới đây là cách triển khai ATMDispenseChain để xử lý số tiền do người dùng yêu cầu.

package com.journaldev.design.chainofresponsibility;

import java.util.Scanner;

public class ATMDispenseChain {

	private DispenseChain c1;

	public ATMDispenseChain() {
		// initialize the chain
		this.c1 = new Dollar50Dispenser();
		DispenseChain c2 = new Dollar20Dispenser();
		DispenseChain c3 = new Dollar10Dispenser();

		// set the chain of responsibility
		c1.setNextChain(c2);
		c2.setNextChain(c3);
	}

	public static void main(String[] args) {
		ATMDispenseChain atmDispenser = new ATMDispenseChain();
		while (true) {
			int amount = 0;
			System.out.println("Enter amount to dispense");
			Scanner input = new Scanner(System.in);
			amount = input.nextInt();
			if (amount % 10 != 0) {
				System.out.println("Amount should be in multiple of 10s.");
				return;
			}
			// process the request
			atmDispenser.c1.dispense(new Currency(amount));
		}

	}

}

Khi chạy ứng dụng trên, ta sẽ nhận được output như sau.

Enter amount to dispense
530
Dispensing 10 50$ note
Dispensing 1 20$ note
Dispensing 1 10$ note
Enter amount to dispense
100
Dispensing 2 50$ note
Enter amount to dispense
120
Dispensing 2 50$ note
Dispensing 1 20$ note
Enter amount to dispense
15
Amount should be in multiple of 10s.

Sơ đồ class

Sơ đồ class cho ví dụ máy rút tiền ATM theo mẫu chuỗi trách nhiệm ở trên sẽ trông như hình dưới đây.

Sơ đồ class mẫu Chain of responsibility

Các điểm cần lưu ý khi dùng mẫu chuỗi trách nhiệm

  • Client không biết phần nào của chuỗi sẽ xử lý request. Nó chỉ cần gửi request đến đối tượng đầu tiên trong chuỗi. Ví dụ, trong chương trình của chúng ta, ATMDispenseChain không hề biết đối tượng nào đang thực sự xử lý yêu cầu rút tiền.
  • Mỗi đối tượng trong chuỗi sẽ có cách hiện thực riêng để xử lý request: xử lý toàn bộ, xử lý một phần, hoặc chuyển tiếp cho đối tượng kế tiếp trong chuỗi.
  • Mỗi đối tượng trong chuỗi phải có một tham chiếu (reference) đến đối tượng kế tiếp để chuyển tiếp request. Điều này đạt được thông qua composition (kỹ thuật tạo đối tượng phức tạp từ nhiều đối tượng thành phần) trong Java.
  • Phải tạo chuỗi một cách cẩn thận, nếu không, có thể xảy ra trường hợp request không bao giờ được chuyển đến một bước xử lý nào đó hoặc không có đối tượng nào trong chuỗi có thể xử lý được request. Trong ví dụ trên, ta đã thêm bước kiểm tra số tiền người dùng nhập vào để đảm bảo nó được xử lý hết bởi tất cả các bộ xử lý. Tuy nhiên, ta cũng có thể không kiểm tra mà thay vào đó sẽ tạo ra lỗi nếu request đã đến đối tượng cuối cùng mà không có đối tượng nào kế tiếp để chuyển tiếp. Đây là một quyết định thuộc về thiết kế.
  • Mẫu chuỗi trách nhiệm dễ đạt được tính loose coupling. Nhưng nó đi kèm với một sự đánh đổi: đó là việc phải tạo ra nhiều class triển khai và có thể gây khó khăn trong việc bảo trì nếu hầu hết các triển khai có nhiều code chung.

Tổng kết

Trên đây là một ví dụ thực tế của mẫu thiết kế chuỗi trách nhiệm. Hy vọng bài viết này đã giúp bạn nắm vững cách thức hoạt động và tiềm năng ứng dụng của nó trong lập trình Java.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất