Java笔记··By/蜜汁炒酸奶

漫谈装饰器模式

定义与目的

装饰器模式(Decorator Pattern)也叫包装器模式(Wrapper Pattern),在不改变原有对象的基础上,动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

原文: Attach additional responsibilities to an object dynaimcally keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

装饰器模式主要是通过组合代替继承,从而解决继承关系过于复杂的问题。主要作用是为原始类添加增强功能,该功能是和原始功能相关的,这也是判断是否改用装饰器模式的一个重要依据。

笼统来说,代理、桥接、装饰器、适配器这4种模式都可以被称为包装器模式(Wrapper Pattern),即通过 Wrapper 类二次封装原始类,具体区别之后再讲。

应用场景

在买奶茶时,我们看到奶茶可以有很多种类,同样一杯饮品也可以有很多种加料(如珍珠、芋圆等)。这些加料便是一种装饰器模式。

Java的IO类库是装饰器模式的一种典型实现,后期单开章节整理。

《设计模式就该这么学》中提到在代码程序中适用于以下应用场景:

  1. 用于扩展一个类的功能,或者给一个类添加附加功能。
  2. 动态地给一个对象添加功能,这些功能可以动态的被撤销。
  3. 需要为一批平行的兄弟类进行改装或加装功能。

由此更能体会到,装饰器模式用来动态地增强原始类的目的。

装饰器模式的UML类图

装饰器的UML类图如下:

umldecoratorbasea.png

由上图可见,装饰器模式一般包括4个角色:

  1. 抽象组件(Component):可以是接口或者抽象类。充当被装饰类的原始对象,规定了被装饰对象的行为。
  2. 具体组件(ConcreteComponent):一个实现/继承 Component 的具体对象,是我们将要动态地加上新型为的对象,即被装饰对象。
  3. 抽象装饰器(Decorator):装饰器共同实现的接口(或者抽象类),每个装饰器内部都有一个属性指向Component (每个组件都可以单独使用,也可以被装饰器包起来使用)。如果系统中装饰逻辑单一,并不需要实现许多装饰器,可以省略该对象,直接实现具体的装饰器即可。
  4. 具体装饰器(ComponentDecorator):Decorator具体实现类,理论上,每一个具体装饰器都扩展类Component对象的一种功能。装饰器可以加上新的方法。新的行为是通过在旧行为前面或者后面做一些计算来添加的。

由上可以看出装饰器实现原理:

  1. 装饰器与组件(即被装饰对象)拥有相同的超类,使得装饰器与被装饰对象类型一致。
  2. 装饰器构造函数中传入被装饰对象,之后装饰器中在调用被装饰对象行为前后添加相关新功能。

通常装饰器模式采用抽象类,但Java中可以使用接口。需根据实际情况决定具体使用什么,毕竟通常都在努力避免修改现有的代码。

通用写法

先看一下的通用写法:

`/**
 * 1. 抽象组件
 */
public abstract class Component {
    /**
     * 方法
     */
    public abstract void operation();
}
/**
 * 2. 具体组件:实现/继承类,被装饰对象
 */
public class ConcreteComponent extends Component{
    @Override
    public void operation() {
        PrintUtill.println("真正的对象做一些事情");
    }
}

/**
 * 3. 具体装饰器A
 */
public class ConcreteComponentA extends Decorator{
    /**
     * 构造方法:传入组件对象
     *
     * @param component
     */
    public ConcreteComponentA(Component component) {
        super(component);
    }
    private void operationOne() {
        PrintUtill.println("装饰器转发请求前的附加功能>>>>>>>>>>>>A");
    }
    private void operationTwo() {
        PrintUtill.println("装饰器转发请求后的附加功能>>>>>>>>>>>>A");
    }

    @Override
    public void operation() {
        operationOne(); // 添加的新功能
        // 可选择性调用父级方法,若不调用,则相当于完全改写了方法,实现新功能
        super.operation();
        operationTwo(); // 添加的新功能
    }
}
/**
 * 4. 具体装饰器B,和A类似,只是添加的新行为不同
 */
public class ConcreteComponentB extends Decorator{
    // ...
}

/**
 * 客户端-用于调用测试
 */
public class DecoratorClient {
    public static void main(String[] args) {
        ConcreteComponentA concreteComponentA = new ConcreteComponentA(new ConcreteComponent());
        concreteComponentA.operation();
        PrintUtill.printlnRule();
        // 嵌套修饰
        ConcreteComponentB concreteComponentB = new ConcreteComponentB(concreteComponentA);
        concreteComponentB.operation();
    }
}
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
66

下面以制作奶茶订单为例,介绍下简单实现:

/**
 * 1.抽象组件-奶茶
 * 首先有个奶茶与装饰器的共同抽象对象
 */
public abstract class Boba {
    // 制作奶茶的抽象方法
    public abstract void make();
}
/**
 * 2. 珍珠奶茶(被装饰类)
 * 新增一种叫珍珠奶茶的奶茶具体实现。
 */
public class TapiocaBoba extends Boba{
    @Override
    public void make() {
        PrintUtill.println("制作珍珠奶茶");
    }
}
/**
 * 3.抽象装饰器-用于奶茶加料
 */
public abstract class BobaDecorator extends Boba{
    private Boba boba;
    public BobaDecorator(Boba boba) {
        this.boba = boba;
    }
    public void make() {
        // 转发制作请求给奶茶组件对象
        boba.make();
    }
}
/**
 * 4. 奶茶具体装饰器A-奶茶加料-椰果
 */
public class CoconutJellyBobaDecorator extends BobaDecorator{

    public CoconutJellyBobaDecorator(Boba boba) {
        super(boba);
    }

    private void operationOne() {
        PrintUtill.println("添加配料:椰果>>>>>>>>>>>>");
    }

    @Override
    public void make() {
        super.make();
        operationOne();
    }
}
/**
 * 5.奶茶具体装饰器-奶茶加料-红豆
 */
public class RedBeansBobaDecorator extends BobaDecorator{
    public RedBeansBobaDecorator(Boba boba) {
        super(boba);
    }

    private void operationOne() {
        PrintUtill.println("添加配料:红豆>>>>>>>>>>>>");
    }
    @Override
    public void make() {
        super.make();
        operationOne();
    }
}
/**
 * 客户端
 */
public class BobaClient {
    public static void main(String[] args) {
        // 用户A定了一杯椰果珍珠奶茶
        CoconutJellyBobaDecorator coconutJellyBoba = new CoconutJellyBobaDecorator(new TapiocaBoba());
        // 制作椰果珍珠奶茶
        coconutJellyBoba.make();
        PrintUtill.printlnRule();
        // 用户B想要一杯椰果红豆珍珠奶茶,
        // 此处为简单演示嵌套装饰,省去了重新创建椰果珍珠奶茶的过程,重用上一个对象。
        RedBeansBobaDecorator redBeansBoba = new RedBeansBobaDecorator(coconutJellyBoba);
        redBeansBoba.make();

    }
}
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

依赖继承VS组合

装饰器模式通过组合代替继承,主要作用是动态地为原始类添加增强功能。但通过上面的UML图以及实现代码可见,装饰器 ComponentDecorator 扩展自抽象组件 Component ,这也是用到了继承。但这里的继承是为了得到正确的类型(也即“类型匹配”),而不是继承它的行为(即利用继承获得行为)。

一般情况下依赖继承意味着继承行为,类的行为只能在编译时静态决定。行为不是来自超类,就是子类覆盖后的版本。每当新增一个行为,都需修改现有代码(如,在奶茶中增加新配料,则可能需要每个类中都增加一个新配料对应属性)。

使用组合,可以在任何时候实现新的装饰器增加新的行为,从这方面讲是“动态地”(如,在奶茶中增加新配料,无需修改之前的代码,只需在新的装饰器中增加相应属性以及相关逻辑即可)。

总结

由上我们可以大致了解:

  1. 装饰器与被装饰对象拥有相同的超类。如此,在任何需要原始类(被装饰类)的场合,都可以用装饰过的对象代替。
  2. 可以用一个或多个装饰器(嵌套装饰)包装一个对象。
  3. 装饰器类可以在委托被装饰类的行为之前/之后,加上自己的行为,以达到特定的目的(主要作用)。
  4. 对象可以在任何时候被修饰,因此可以在运行时动态地/不限量的用喜欢的装饰器来装饰对象。

与代理模式区别

  1. 装饰器模式是增强原始类自身功能,主体对象是被装饰类。
  2. 代理模式强调访问控制,附加的是与原始类自身功能无关的功能。代理类可以决定对功能进行扩展、缩减甚至消失(不调用真实对象的相应方法),主体对象是代理类。

装饰器模式的优点

  1. 对继承的补充,通过组合,比继承更灵活。在不改变原有对象的情况下,动态扩展对象功能。
  2. 通过使用不同的装饰器以及这些类的排列组合(嵌套装饰),可以实现不同的效果。
  3. 装饰器模式完全遵循开闭原则。

装饰器模式的缺点

  1. 会导致设计中出现许多小对象(也会出现更多代码、更多的类),若过度使用会让程序变得更复杂。
  2. 状态装饰在多层装饰(即嵌套装饰)时会更复杂。

参考资料:

  1. 设计模式之美
  2. 《Head First 设计模式》
  3. 《设计模式就该这么学》
  4. 《Java设计模式及实践》

JK_DesignPattern.png

Preview
Loading comments...
1 条评论
  • comment-avatar

    66666

example
Preview