The purpose of the Chain of Responsibilities model
The Chain of Responsibility pattern is designed to create a series of sequential processors. Each handler in the chain can terminate the chain continuation at any point, which means that only some of the handlers in the chain need to process data. In addition, the order of the chain can be freely composed according to the requirements.
What are the benefits to be gained from it?
- Single responsibility: Each processor is responsible for one thing.
- Controlled processing sequence.
- Open-closed principle: New handlers can be added without breaking existing code.
- Handlers can process data or pass it to the next handler based on runtime conditions.
implement
Abstract classes may be used when implementing this pattern. But if possible, it's better to use an interface. You may also encounter implementations that don't have the setNext(handler: Handler) method, but instead pass the Handler implementation through the constructor.
Typically, when implementing this pattern, only one processor will process the data, and the others will not. This is often used in the UI. For example, in Compose, you might want to pass the onClick handler lambda up the hierarchy.
example
Calculate how many banknotes the ATM will withdraw when someone tries to withdraw money. If someone wants to withdraw $58, you can give him 1 $50, 1 $5, and 3 $1 bills, for a total of $58. This can be achieved through the Chain of Responsibility pattern:
For the sake of simplification, ATMs only support $50, $5, and $1 bills.
First, create an interface:
interface ATM {
fun withdraw(amount: Int)
fun setNext(atm: ATM)
}
Now it's the specific ATM. All the code is very similar.
class ATM50Dollars : ATM {
private var nextATM: ATM? = null
override fun withdraw(amount: Int) {
println("提取 ${amount / 50} 张50美元钞票")
nextATM?.withdraw(amount % 50)
}
override fun setNext(atm: ATM) {
nextATM = atm
}
}
class ATM5Dollars : ATM {
private var nextATM: ATM? = null
override fun withdraw(amount: Int) {
println("提取 ${amount / 5} 张5美元钞票")
nextATM?.withdraw(amount % 5)
}
override fun setNext(atm: ATM) {
nextATM = atm
}
}
class ATM1Dollar : ATM {
private var nextATM: ATM? = null
override fun withdraw(amount: Int) {
println("提取 $amount 张1美元钞票")
nextATM?.withdraw(0)
}
override fun setNext(atm: ATM) {
nextATM = atm
}
}
In this case, if you don't take out all the money, you might want to throw a mistake as well. A given object may not be able to handle a given task. Prevent this from happening.
Now, let's look at an example of how to use it:
fun main() {
val atm1: ATM = ATM1Dollar()
val atm50: ATM = ATM50Dollars()
atm50.setNext(atm1)
atm50.withdraw(58) // 1 x 50$ + 8 x 1$
val atm5: ATM = ATM5Dollars()
atm50.setNext(atm5)
atm5.setNext(atm1)
atm50.withdraw(58) // 1 x 50$ + 1 x 5$ + 3 x 1$
atm5.withdraw(58) // 11 x 5$ + 3 x 1$
}
This pattern may make your program more complex, but it reduces code coupling and allows you to adapt quickly to changes in requirements.
This pattern is similar to that of decorators. The only real difference is that one of the processing classes can return the result in the chain of responsibility. Instead, all composite classes must process the data in the decorator.