设计模式

设计模式

每一个模式描述了在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次的使用该方案而不用重复劳动。

工厂模式

实际上工厂模式常规来说分为简单工厂模式和抽象工厂模式,后者是前者的升级模式,所以这里直接针对后者来说。

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类名。

  • 因为不需要指定具体的类名,增加了系统拓展性,降低代码之间的耦合。
  • 切割代码,使得具体化类的工作延迟到了子类中。这也是抽象的意义。

    当需要不同的产品时,抽象工厂屏蔽了实现的细节,客户端不直接和具体工厂打交道。当产品种类需要修改或者变更时,客户端不会收到影响。或者像更换主题一样,更换另一套产品时只需要更改调用参数,对客户端影响很小。

    组成

  1. 抽象工厂,定义了生产的产品线。
  2. 具体工厂,实现了不同产品线的生产方式。
  3. 抽象产品,定义产品主要功能。
  4. 具体产品。
  5. 客户端。

    效果

  6. 分离了具体的类,降低耦合
  7. 易于更换产品序列
  8. 有利于产品的一致性,产品永远是同序列的。
  9. 难以支持新的产品线。产品线是在抽象抽象工厂中定义,如果更改会对所有具体工厂产生影响。

    代码

1
2
3
4
5
6
7
/**
* 抽象工厂类,规定了两条产品线
*/
public interface Factory {
public Phone makePhone();
public Computer makeComputer();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 具体工厂
*/
public class HuaweiFactory implements Factory {
@Override
public Phone makePhone() {
return new HuaweiPhone();
}

@Override
public Computer makeComputer() {
return new HuaweiComputer();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 具体工厂
*/
public class XiaomiFactory implements Factory {
@Override
public Phone makePhone() {
return new XiaomiPhone();
}

@Override
public Computer makeComputer() {
return new XiaomiComputer();
}
}
1
2
3
4
5
6
/**
* 抽象产品
*/
public interface Phone {
public void call();
}
1
2
3
4
5
6
/**
* 抽象产品
*/
public interface Computer {
public void compute();
}
1
2
3
4
5
6
7
8
9
/**
* 具体产品
*/
public class HuaweiComputer implements Computer {
@Override
public void compute() {
System.out.println("HuaweiComputer computing");
}
}
1
2
3
4
5
6
7
8
9
/**
* 具体产品
*/
public class HuaweiPhone implements Phone {
@Override
public void call() {
System.out.println("HuaweiPhone calling");
}
}
1
2
3
4
5
6
7
8
9
/**
* 具体产品
*/
public class XiaomiComputer implements Computer {
@Override
public void compute() {
System.out.println("XiaomiComputer computing");
}
}
1
2
3
4
5
6
7
8
9
/**
* 具体产品
*/
public class XiaomiPhone implements Phone{
@Override
public void call() {
System.out.println("Mix2S calling");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 客户端
*/
public class FactoryClient {
private Factory factory;

public Phone buyPhone(int i){
switch (i){
case 1:
factory = new HuaweiFactory();
break;
case 2:
factory = new XiaomiFactory();
break;
default:
}
return factory.makePhone();
}

public static void main(String[] args) {
FactoryClient factoryClient = new FactoryClient();
Phone phone = factoryClient.buyPhone(1);
phone.call();
Phone phone2 = factoryClient.buyPhone(2);
phone2.call();

}
}

单例模式

保证一个类的对象只有一个实例,并提供一个全局能访问到实例的方法

效果

  1. 对唯一实例访问的控制
  2. 缩小命名空间,避免全局变量污染空间
  3. 在内存里只有一个实例,减少了内存的开销
  4. 允许可变数目的实例
  5. 难以拓展,一般直接重新写
  6. 提供工厂方法,职责过重

组成

  1. 私有化构造方法
  2. 内部维护对象实例
  3. 暴露全局方法获取实例对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 饿汉式单例模式
*/
public class Singleton implements Serializable {
// 内部维护实例引用,必须是static。volatile 保证 创建对象 的原子性。
private static volatile Singleton instance;
// 私有化构造方法
private Singleton(){
if(instance != null){
throw new RuntimeException("非法访问构造方法");
}
}

// 双重检查比直接方法加锁效率更高
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}

// 避免序列化创造出多个实例
// 不明白为什么这个方法不是放在Serializable接口里显式规定,可能是为了避免代码入侵。但是目前这样也太不保险了。
private Object readResolve() throws ObjectStreamException {
return instance;
}
}

class LazySingleton{
private static LazySingleton instance = new LazySingleton();
private LazySingleton(){}
public static LazySingleton getInstance(){
return instance;
}
}

class StaticSingleton{
private static class Holder{
private static final StaticSingleton INSTANCE = new StaticSingleton();
}

private StaticSingleton(){}

public static StaticSingleton getInstance(){
return Holder.INSTANCE;
}
}
// 枚举是最安全也是推荐的实现单例方式,避免反射攻击
enum EnumSingleton{

INSTANCE("张三");

private String name;

EnumSingleton(String name){
this.name = name;
}
public String getName(){
return name;
}
}

适用

  1. 实例耗费资源,一个实例足够使用。例如数据库驱动,配置文件。

思考

安全性

  1. Java 中通过private来限制对单例类构造方法的调用,但是实际上可以通过反射来访问到构造方法,从而创造出更多实例。此时应该在构造方法中判断实例不为空则抛出异常。

    1
    2
    3
    4
    5
    private Singleton(){
    if(instance != null){
    throw new RuntimeException();
    }
    }
  1. 单例对象序列化传递后,反序列化仍然可以创建出多个实例。序列化接口没有具体方法,但是规定了几个特定签名方法,可以解决单例反序列化问题

    1
    2
    3
    private Object readResolve() throws ObjectStreamException {
    return instance;
    }

Spring 中的实现

spring中单例模式并没有按照标准要求将Bean的构造方法私有化。实际上spring是在一个map中存储了所有bean实例,在需要注入时,从map中取出bean实例,否则才会创建bean。虽然和传统单例实现方式完全不一样,但是也达到了同样的目的。

适配器模式

将一个接口转换为客户希望的另一个接口形式。

效果

  1. 可以让任何两个没有关联的类一起运行。
  2. 提高了类的复用。
  3. 增加了类的透明度。
  4. 灵活性好。
  5. 过多地使用适配器,会让系统非常零乱,李代桃僵。
  6. 由于 Java 至多继承一个类,所以至多只能适配一个适配者类

组成

  1. 目标接口
  2. 被适配的类
  3. 适配器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 适配器
* 类适配器通过继承类实现,对象适配器通过委托来实现
*/
public class Adapter implements Cat{

private Dog dog;

public Adapter(Dog dog){
this.dog = dog;
}

@Override
public void catchMouse() {
dog.bark();
}

}
1
2
3
4
5
6
/**
* 目标接口
*/
public interface Cat {
public void catchMouse();
}
1
2
3
4
5
6
7
8
/**
* 被适应的类
*/
public class Dog {
public void bark(){
System.out.println("Woo");
}
}

除了这(俩)种适配模式,还有一种缺省适配器。

1
2
3
4
5
6
7
8
9
/**
* 缺省适配器目标类,有很多方法需要实现
*/
public interface Servlet {
void get();
default void post(){
System.out.println("post method");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 缺省适配器首先对所有方法进行默认实现,子类只需要实现自己关心的方法
*/
public abstract class DefaultAdapter implements Servlet{
@Override
public void get() {

}

@Override
public void post() {

}
}
1
2
3
4
5
6
7
8
9
/**
* 接口使用默认实现后,本类可以直接实现接口,取消掉继承
*/
public class ConcreteServlet extends DefaultAdapter {
@Override
public void get() {
System.out.println("get method");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
//对象或者类适配器
Cat cat = new Adapter(new Dog());
cat.catchMouse();

//缺省适配器
Servlet servlet = new ConcreteServlet();
servlet.get();
}
}

适用

  1. 需要使用一个已经存在但是不符合要求的类
  2. 创建一个类,保持它以后与其他类的兼容性
  3. 对象适配,不必须修改所有子类,直接适配父类即可,使用时传入子类对象

思考

实际上这是对组合和继承联合使用,得到一种欺骗的目的,从类的继承关系上来看是A类,实现却是B类。因为有个委托的关系,和代理模式有很大相似。

缺省适配器在被适配的类中有很多方法,但是实现类只关心少数的时候使用。屏蔽了不关心的方法。但是在JDK 8中增加了接口的默认实现,直接吸收了这种特性。子类不必须实现全部接口方法。实际上示例代码中接口使用了default实现,这种情况下,抽象类不需要存在了,子类直接实现接口就好。

装饰器模式

动态得给一个对象增加一些额外的功能,比继承更加灵活。

效果

  1. 比继承更加灵活,不存在final,多继承的限制。
  2. 避免了层次高的对象有太多方法。
  3. 改变了原有对象,装饰器和被装饰不再是一个对象,不能依赖原有对象。
  4. 因为组合方式的不同产生比较多的小对象,容易造成混乱。

组成

  1. 抽象部件
  2. 具体部件
  3. 装饰器
1
2
3
public interface Component {
void action();
}
1
2
3
4
5
6
public class ConcreteComponent implements Component {
@Override
public void action() {
System.out.println("Concrete action");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Decorator implements Component{

private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void action() {
enhance();
component.action();
}

private void enhance(){
System.out.println("enhance something");
}
}
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
// 运行时指定被装饰的目标对象,可以达到动态组合的目的以加强不同的功能。和代理模式的主要区别
Decorator decorator = new Decorator(component);
decorator.action();
}
}

适用

  1. 在不影响其他对象的情况下,给单个对象添加额外的功能
  2. 使用继承的方式不够灵活的时候。
  3. 可以撤销的功能。

思考

装饰器模式和代理模式区别并不大。代理模式更加突出的是封闭被代理对象,使得被代理对象完全不向外界暴露,代理类和被代理对象在定义类的时候编译器就已经确定。装饰器更多的动态组合,需要手动传入被装饰的对象,需要在施用的时候指定,并且可以改变被装饰的对象实例。二者都是增加了目标类的功能。

装饰模式的本质是动态组合。动态是手段,组合是目的。每个装饰类可以只负责添加一项额外功能,然后通过组合为被装饰类添加复杂功能。由于每个装饰类的职责比较简单单一,增加了这些装饰类的可重用性,同时也更符合单一职责原则。

外观(门面)模式

为子系统中一组接口提供一个一致的界面,外观模式定义了一个高层次的接口使得这一子系统更加容易使用。

效果

  1. 对客户屏蔽了子系统的实现细节,只保留接口。
  2. 降低了子系统和客户之间的耦合
  3. 保留了客户直接调用子系统内部实现的可能

组成

  1. facade 收集整理子系统实现细节,对外暴露组合方法
  2. 子系统,提供功能的实现细节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ModuleA {
public void first(){
System.out.println("先淘米");
}
public void second(){
System.out.println("再开火");
}
}

public class ModuleB {
public void first(){
System.out.println("洗菜");
}
public void second(){
System.out.println("炒菜");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 也可以暴露子系统的实现细节
*/
public class Facade {
private ModuleA a = new ModuleA();
private ModuleB b = new ModuleB();

public void cook(){
a.first();
a.second();
b.first();
b.second();
}
}
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.cook();
}
}

适用

  1. 为复杂子系统提供简单接口
  2. 降低子系统之间的耦合性,避免直接的系统内部依赖
  3. 提高子系统的隔离,移植性

思考

就像整理数据线,把不同的数据线困扎一下,对外只暴漏一个线头。没有必要直接顺着线去找每一根线从哪里出来,只要根据最外面线头标志适用就可以。同时也可以顺着线头往下找,自己组合一个新的功能出来。

外观模式可以有点像抽象工厂模式,并且可以混用。由抽象工厂来提供一个独立的对象,该对象封装了子系统对外的组合接口,同时隐藏内部实现细节。

代理模式

为目标对象提供一种代理以控制目标对象的访问

效果

  1. 屏蔽对象细节,不必直接去管理目标对象
  2. 代替耗时对象,推迟耗时操作,避免直接阻塞。
  3. 增强功能。
  4. 系统可能会很复杂,增加复杂度。
  5. 大量额外工作可能会降低客户端相应速度

组成

  1. 接口。定义了被代理类和代理类的共同方法。
  2. 被代理对象,接口的真正实现。
  3. 代理对象,包含被代理对象的引用并委托其进行真正的操作。
1
2
3
4
5
6
7
8
9
public interface Subject {
void update();
}
public class RealSubject implements Subject {
@Override
public void update() {
System.out.println("update something");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Proxy implements Subject{
private Subject subject;

public Proxy(){
// 自行控制被代理对象的创建过程,也可以放在真正需要调用被代理对象的时候创建实现延迟加载。
this.subject = new RealSubject();
}

@Override
public void update() {
checkPermission();
subject.update();
}

private void checkPermission(){
System.out.println("Permission grant");
}
}
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.update();
}
}

适用

  1. 远程代理,屏蔽网络细节。
  2. 保护代理,可以在运行期进行权限检查,核实后将调用传递给被代理的对象。
  3. 智能引用代理,在访问一个对象时,执行一些内务处理(Housekeeping)操作,比如记录日志等
  4. 虚拟代理,使用虚拟代理模式的优点是代理对象可以在必要的时候才将被代理的对象加载。延迟加载模式。

思考

代理模式非常流行,JDK1.3后将代理模式纳入了JDK,并使用Proxy实现了动态代理。动态代理和静态代理最大的区别在于代码中规定了动态代理类的行为,但是没有定义动态代理类,动态代理类是在运行时动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

InvocationHandler实际上是目标类的一个静态代理类。invoke方法最终委托给了被代理对象。而运行时生成的动态代理类又是 InvocationHandler的一个静态代理类,动态代理类的方法调用委托给了invoke。所以实际上JDK的动态代理就是二层静态代理。

模板模式

定义一个算法的骨架,其具体的实现算法步骤推迟到子类中实现。

效果

  1. 代码复用
  2. 反向控制
  3. 拓展性强
  4. 每一个实现都会产生一个子类,增加系统复杂性

组成

  1. 抽象类,原语方法必须由子类实现,钩子方法不强制要求,抽象类中对钩子方法做默认或者简单实现。
  2. 实现类,实现抽象的基础方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Template {
public void cook(){
rice();
vegetable();
tea();
}
//原语函数,必须实现
public abstract void vegetable();
public abstract void rice();
//钩子函数,不强制要求实现
public void tea(){
System.out.println("after dinner");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Concrete extends Template {
@Override
public void vegetable() {
System.out.println("cook vegetable");
}

@Override
public void rice() {
System.out.println("cook rice");
}
}
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Template template = new Concrete();
template.cook();
}
}

适用

  1. 一次性实现算法的不变部分,并将可变部分留给子类实现。
  2. 提取子类中共同的部分,以避免重复代码
  3. 控制子类拓展

思考

Java中抽象类体现的就是模板模式

策略模式

定义一系列的算法,把它们封装起来并且可以互相替换,算法可以独立于客户端变化

效果

  1. 一系列相关算法形成系列
  2. 作为继承的替代方案
  3. 消除一些条件语句
  4. 已经实现了不同的算法可供客户端根据实际情况选择
  5. 客户端必须了解每一个策略
  6. 策略与上下文之间的通讯开销
  7. 增加了对象的数目

组成

  1. 策略上下文,用于提供策略需要的数据,和客户端交互。
  2. 抽象策略
  3. 具体策略
1
2
3
4
5
public interface Strategy {

void resolveEquation();

}
1
2
3
4
5
6
public class FormulaStrategy implements Strategy {
@Override
public void resolveEquation() {
System.out.println("公式法");
}
}
1
2
3
4
5
6
public class EliminationStrategy implements Strategy{
@Override
public void resolveEquation() {
System.out.println("消元法");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Context {
private Strategy strategy;

public Context(Strategy strategy){
this.strategy = strategy;
}

public void execute(){
strategy.resolveEquation();
}
}
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Context context = new Context(new FormulaStrategy());
context.execute();
Context context2 = new Context(new EliminationStrategy());
context2.execute();
}
}

适用

  1. 许多相关的类仅仅是行为上有所差别
  2. 需要使用一个算法的不同变体
  3. 算法使用的数据不希望暴露给客户端
  4. 消除方法中有多个if-else

思考

策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

模板模式使用继承来改变算法的一部分。 策略模式使用委托来改变整个算法。模板模式与策略模式的作用十分类似,有时可以用策略模式替代模板模式。模板模式通过继承来实现代码复用,而策略模式使用委托,把不确定的行为集中到一个接口中,并在主类委托这个接口。委托比继承具有更大的灵活性。

策略模式强调算法系列之间的平等性,整个算法系列都可以互相替换,体现对算法系列的管理。模板模式强调一个算法本身的运行,规定了一个算法自身内部的执行顺序,允许部分节点可以被替换。

观察者模式

定义一种对象之间的一对多的关系,当被观察者的状态发生变化时,所有的观察者都能得到通知。

效果

  1. 被观察者和观察着之间的松耦合。如果二者层次不同,不会破坏系统结构。
  2. 可以方便广播
  3. 意外的更新。拉模式中,一般会将被观察者传递给观察者,观察者不知道其他观察者的存在,如果他修改了目标导致其他观察者的变化不可知的。

组成

  1. 抽象目标,规定添加、删除、通知观察者的方法
  2. 具体目标,维护观察者的引用。
  3. 抽象观察者,定义接受通知的方法。
  4. 具体观察者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 按照规范来说这里应该拆为一个接口和一个实现类,但是我并没有体会到拆分的好处。规范是为了开发便利服务,而不是相反。
* 我现在感觉到不拆分更便利,所以我不拆分。
*/
public class Subject {
private List<Observer> observerList = new ArrayList<>();

public void add(Observer observer){
this.observerList.add(observer);
}

public void del(Observer observer){
this.observerList.remove(observer);
}

private void notifyObserver(){
for (Observer observer : observerList) {
observer.change();
}
}
public void changeState(){
notifyObserver();
}
}
1
2
3
4
5
6
7
8
/**
* 按照规范来说这里应该拆为一个接口和一个实现类
*/
public class Observer{
public void change(){
System.out.println("something changed");
}
}
1
2
3
4
5
6
7
8
public class Clinet {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer = new Observer();
subject.add(observer);
subject.changeState();
}
}

适用

  1. 一个系统有两个不同的方面,其中一个方面依赖另一个。
  2. 一个对象改变需要通知其他对象,但是不知道有多少对象要被通知。
  3. 当一个对象要通知一个不确定的对象。

思考

推模式的情况下假定目标了解观察者需要知道的信息,这种情况下,目标只需要推送信息,不管观察者是否真的需要,是否会得到处理。这里的局限在于观察者所需要的信息并不一定一样。

拉模式常见的实现是将目标直接当作参数传递给观察者,观察者自己获取关心的消息。可能会导致对其他观察者的影响。

通知观察者的时机也值得讨论,如果通知由目标状态改变时自动触发可能会在多次状态变化时多次通知观察者。如果有客户端在修改目标状态后手动通知,会承担忘记通知的风险。

命令模式

将一个请求封装成为一个对象,可以使用不同的请求对客户进行参数化;对请求排队,记录日志,或者支持可撤销的操作

效果

  1. 调用命令的对象和实现命令的对象解耦
  2. 可以实现命令的组合
  3. 容易扩展命令
  4. 容易动态控制,包括排队,日志,参数化

组成

  1. 客户端,创建具体命令对象,指定接收者。
  2. 抽象命令,指定接口方法。
  3. 具体命令,定义一个接收者和行为之间的弱耦合,负责调用接收者的相应操作。
  4. 请求者,调用命令对象执行相关方法。
  5. 接收者,负责具体执行一个请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 持有命令对象,发起请求后委托其处理
*/
public class Invoker {
private Command command;

public Invoker(Command command){
this.command = command;
}

public void action(){
command.execute();
}
}
1
2
3
public interface Command {
void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 持有真正的执行者引用,知道如何委托执行者处理请求
*/
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
receiver.realAction();
}
}
1
2
3
4
5
public class Receiver {
public void realAction(){
System.out.println("命令的最终执行方法,receiver");
}
}
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker(new ConcreteCommand(new Receiver()));
invoker.action();
}
}

适用

  1. 抽象出待执行的动作以参数化某对象,替代回调
  2. 在不同的时刻指定,排列和执行请求
  3. 支持取消操作
  4. 命令持久化保证高可靠
  5. 利用原语构建高层系统

思考

可以通过保存一些额外的信息来提供命令对应的取消操作,包括接收者对象以及对应的取消操作所需参数。如果改变了接收者的状态,还需要保存接收者的状态。

责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者和处理者之间的耦合关系。将这些对象连成一条链,沿着链传递请求,知道有一个对象处理。

效果

  1. 降低耦合度,客户端和具体的处理者没有直接关系。链中任何一环不需要知道整个链,只需要知道后继者。
  2. 增加了处理器指派指责的灵活性,可以在运行时对链条动态修改。
  3. 不能保证请求一定会被处理。
  4. 链多导致效率低
  5. handler接口规定了处理器,降低拓展性

组成

  1. handler 接口,定义处理方法,实现后继链条。
  2. 具体handler,能处理请求,能访问后继者,要么处理,要么后传
  3. client 向链条中的一环提交请求
1
2
3
4
5
6
7
8
9
10
11
public interface Leader {

// 维护后继者引用
Leader getMyLeader();

//例子相关,额外业务,和模式无关
boolean canHandle(int money);

//真正处理逻辑
boolean handle(int money);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Group implements Leader {
private Leader leader;

public Group(Leader leader){
this.leader = leader;
}

@Override
public Leader getMyLeader() {
return leader;
}

@Override
public boolean canHandle(int money) {
return money > 0 && money < 500;
}

@Override
public boolean handle(int money) {
if(canHandle(money)){
System.out.println("组长报销");
return true;
}else{
System.out.println("组长权限不足,向上申请");
return getMyLeader().handle(money);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Manager implements Leader {

private Leader leader;

public Manager(Leader leader){
this.leader = leader;
}

@Override
public Leader getMyLeader() {
return leader;
}

@Override
public boolean canHandle(int money) {
return money > 0 && money < 1000;
}

@Override
public boolean handle(int money) {
if(canHandle(money)){
System.out.println("经理报销");
return true;
}else{
System.out.println("经理权限不足,向上申请");
return getMyLeader().handle(money);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CFO implements Leader {


@Override
public Leader getMyLeader() {
return null;
}

@Override
public boolean canHandle(int money) {
return true;
}

@Override
public boolean handle(int money) {
System.out.println("CFO 报销");
return true;
}
}
1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
int money = 900;
Leader CFO = new CFO();
Leader manager = new Manager(CFO);
Leader group = new Group(manager);

group.handle(money);
}
}

适用

  1. 有多个对象可以处理一个请求,具体处理者在运行时确定
  2. 需要在不明确指定接收者的情况下,向多个对象中某一个提交请求
  3. 可以处理一个请求的对象集合是动态指定的

思考

请求是直接的方法调用,这种方式比较安全,但是只能转发到handler定义的固定的一组请求。

请求方式是传递参数,然后手动判断调用方式更为灵活,但是不太安全。

纯的责任链要求每个链要么处理要么传递,不允许既处理又传递,并且最终必须要有一个链来处理请求。但是现实应用中一般都是不纯的责任链,并不能保证这两条规则。