GeekIBLi

设计模式之美-装饰器模式

2021-08-07

装饰器模式(Decorator Pattern)

装饰器模式也叫做包装模式,是指在不改变原有对象的基础上,将功能附加到对象上,提供比继承更有弹性的替代方案(扩展原有对象的功能),强调一点是基于已有对象的功能增强;装饰器模式属于结构性模式;

装饰器类图

Component 抽象构件

定义一个抽象接口以规范准备接受附加责任的对象

1
2
3
public abstract class Component {
public abstract void operation();
}

ConcreteComponent 具体的构件

实现抽象构件,通过装饰角色为其添加一些职责

1
2
3
4
5
6
public class ConcreteComponent extends Component{
@Override
public void operation() {
System.err.println("Do some biz event!");
}
}

Decorator 抽象装饰角色

继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Decorator extends Component{

//持有组件对象
protected Component component;

public Decorator(Component component) {
this.component = component;
}

@Override
public void operation() {
//转发请求给组件对象,可以在转发前后执行一些附加动作
component.operation();
}
}

ConcreteDecoratorA

实现抽象装饰的相关方法,并给具体构件对象添加附加的责任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ConcreteDecoratorA extends Decorator {

public ConcreteDecoratorA(Component component) {
super(component);
}

public void operationFirst() {
System.err.println("ConcreteDecoratorA first!");
}

public void operationLast() {
System.err.println("ConcreteDecoratorA last!");
}

@Override
public void operation() {
operationFirst();
super.operation();
operationLast();
}
}

Client 测试类

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Decorator decoratorA = new ConcreteDecoratorA(component);
decoratorA.operation();
}
}

举个例子

买煎饼的时候,我们可以直接买一个煎饼,但有的时候觉得味道单一或者吃不饱,我们可以加一些东西,比如烤肠,鸡蛋,鸡排等等;

先看看非装饰器模式的写法

BatterCake

就一个普通的煎饼类;

1
2
3
4
5
6
public class BatterCake {
protected String getMsg(){return "煎饼";}
public int getPrice(){
return 10;
}
}

BatterCakeWithEgg

添加一个鸡蛋的煎饼;

1
2
3
4
5
6
7
8
9
public class BatterCakeWithEgg extends BatterCake{
protected String getMsg(){
return super.getMsg() + " 加一个鸡蛋";
}

public int getPrice(){
return super.getPrice() + 1;
}
}

BatterCakeWithEggAndSauage

加一个鸡蛋 再加一跟烤肠的煎饼;

1
2
3
4
5
6
7
8
9
public class BatterCakeWithEggAndSauage extends BatterCakeWithEgg{
protected String getMsg(){
return super.getMsg() + " 加一个烤肠";
}

public int getPrice(){
return super.getPrice() + 2;
}
}

Client 客户

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
BatterCake batterCake = new BatterCake();
System.err.println(batterCake.getMsg() + "价格 " + batterCake.getPrice());

BatterCakeWithEgg batterCakeWithEgg = new BatterCakeWithEgg();
System.err.println(batterCakeWithEgg.getMsg() + "价格 " + batterCakeWithEgg.getPrice());

BatterCakeWithEggAndSauage batterCakeWithEggAndSauage = new BatterCakeWithEggAndSauage();
System.err.println(batterCakeWithEggAndSauage.getMsg() + "价格 " + batterCakeWithEggAndSauage.getPrice());
}
}

看到这种写法,应该能看出它的弊端了,就是实现很简单,适合于需求固定的业务和多样性比较简单的业务;一旦客户需要一个鸡蛋,两根烤肠,那是不是还要在BatterCakeWithEggAndSauage类上继续扩展呢,这其实是很low的设计;而且非常不灵活,比如客户只需要一跟烤肠的煎饼。这个时候怎么解决呢;

使用装饰器模式优化

BatterCake 抽象组件

1
2
3
4
public abstract class BatterCake {
protected abstract String getMsg();
protected abstract int getPrice();
}

基础类 实现抽象接口

1
2
3
4
5
6
7
8
9
10
11
public class BaseBatterCake extends BatterCake{
@Override
protected String getMsg() {
return "煎饼";
}

@Override
protected int getPrice() {
return 5;
}
}

BatterCakeDecorator 装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BatterCakeDecorator extends BatterCake{

private BatterCake batterCake;

public BatterCakeDecorator(BatterCake batterCake) {
this.batterCake = batterCake;
}

@Override
protected String getMsg() {
return this.batterCake.getMsg();
}

@Override
protected int getPrice() {
return this.batterCake.getPrice();
}
}

EggDecorator 具体的装饰器对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EggDecorator extends BatterCakeDecorator{

public EggDecorator(BatterCake batterCake) {
super(batterCake);
}

@Override
protected String getMsg() {
return super.getMsg() + " 加一个鸡蛋";
}

@Override
protected int getPrice() {
return super.getPrice() + 1;
}
}

SauageDecorator 具体的装饰对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SauageDecorator extends BatterCakeDecorator{

public SauageDecorator(BatterCake batterCake) {
super(batterCake);
}

@Override
protected String getMsg() {
return super.getMsg() + " 加一个烤肠";
}

@Override
protected int getPrice() {
return super.getPrice() + 2;
}
}

客户 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {

public static void main(String[] args) {

BatterCake batterCake = new BaseBatterCake();
System.err.println(batterCake.getMsg() + "总计 : " + batterCake.getPrice());

batterCake = new EggDecorator(batterCake);
System.err.println(batterCake.getMsg() + "总计 : " + batterCake.getPrice());

batterCake = new SauageDecorator(batterCake);
System.err.println(batterCake.getMsg() + "总计 : " + batterCake.getPrice());

batterCake = new EggDecorator(batterCake);
System.err.println(batterCake.getMsg() + "总计 : " + batterCake.getPrice());
}

}

上面这种写法是运用了装饰器模式的写法,这样会增加程序的灵活性,EggDecorator返回一个BatterCake,在EggDecorator中的getMsg方法和getPrice方法中添加关于Egg的逻辑来实现对BatterCake的增强,同理SauageDecorator也是,在它自己的getMsg方法和getPrice方法中添加自己的逻辑,当然,都是基于调用super方法的基础上添加自己的逻辑,同时具体的装饰对象返回父类类型的对象;

1
2
3
4
@Override
protected int getPrice() {
return super.getPrice() + 2;
}

这里进行一下总结:
1、具体装饰对象(EggDecorator)一定是继承自装饰组件(BatterCakeDecorator)
2、为了实现对象增强,子类中的方法一定是基于super方法的基础上,添加自己的逻辑的

使用场景

  • 用于扩展一个类的功能或给一个类添加附加职责
  • 动态地给一个对象添加功能,这些功能可以在动态的撤销

实际应用

Spring TransactionAwareCacheManager
JDK FileInputStream

装饰器模式与代理模式

  • 装饰器模式是一种特殊的代理模式
  • 装饰器模式强调自身的功能扩展,透明的,动态的扩展与增强

    透明指的是功能的扩展由客户端控制

  • 代理模式强调代理过程的控制

装饰器模式的优点

1、装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
2、通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
3、装饰器模式完全遵守开闭原则

装饰器模式的缺点

1、增加了一些子类,系统代码会显得臃肿。
2、组合方式容易出错,代码可读性比较差。

参考文档

1、包装模式就是这么简单啦
2、装饰器模式(装饰设计模式)详解
3、java中的装饰设计模式,浅谈与继承之间的区别
4、JDK IO中的适配器模式和装饰者模式