概述

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装。

若要扩展一个对象的功能,装饰器模式提供了比继承更有弹性的替代方案。组合优于继承

何时使用:

  • 需要扩展一个类的功能,或给一个类增加附加责任。
  • 需要动态的给一个对象增加功能,这些功能可以再动态地撤销。
  • 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得不现实。

UML 类图:

设计模式——装饰器模式_ide

角色组成:

  1. 抽象构件角色(Component): 定义可以动态添加任务的对象的接口
  2. 具体构件角色(ConcreteComponent):定义一个要被装饰器装饰的对象,即 Component 的具体实现
  3. 抽象装饰器(Decorator): 持有一个构件(Conponent)对象的实例,并定义一个和抽象构件一致的接口。维护对组件对象和其子类组件的引用
  4. 具体装饰器角色(ConcreteDecorator):向组件添加新的职责


通用代码

抽象构件角色:

public interface Component {
void operation();
}

具体构件角色:

public class ConcreteComponent implements Component {
@Override
public void operation() {
// 具体业务代码
System.out.println("ConcreteComponent 通用基础逻辑");
}
}

抽象装饰角色:

public abstract class Decorator implements Component {
private Component component;

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

@Override
public void operation() {
// 委派给构件
component.operation();
}
}

具体装饰角色:

public class ConcreteDecorationA extends Decorator {

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

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

// 定义自己的修饰逻辑
private void decorateMethod() {
System.out.println("ConcreteDecorationA 的修饰逻辑");
}
}

public class ConcreteDecorationB extends Decorator {

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

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

// 定义自己的修饰逻辑
private void decorateMethod() {
System.out.println("ConcreteDecorationB 的修饰逻辑");
}
}

使用:

public class Test {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Decorator decoratorA = new ConcreteDecorationA(component);
decoratorA.operation();
}
}

结果:

ConcreteComponent 通用基础逻辑

ConcreteDecorationA 的修饰逻辑

看到这里,相信大家对装饰器模式的通用代码结构清楚了许多,下面我们通过一个实际使用案例来进一步诠释。

实例

相信大家都喝过奶茶,奶茶的种类有很多种,比如有椰香奶茶、QQ奶茶,巧克力奶茶等,奶茶中又可以添加椰果、红豆、布丁等不同的甜品。奶茶店现在要卖各种口味的奶茶,如果不使用装饰模式,那么在销售系统中,各种不一样的奶茶都要产生一个类,如果有5种奶茶类,5种甜品,那么就会产生至少25个类(不包括混合口味),如果使用了装饰模式,那么几个类就可以搞定了。

奶茶接口类

public interface MilkTea {
// 返回奶茶描述
String getDescription();
// 返回价格
double getPrice();
}

具体的奶茶类:巧克力奶茶、QQ奶茶

public class ChocolateMT implements MilkTea{
private String description = "巧克力奶茶";
@Override
public String getDescription() {
return description;
}

@Override
public double getPrice() {
return 15;
}
}


public class QQMT implements MilkTea {
private String description = "QQ奶茶";
@Override
public String getDescription() {
return description;
}

@Override
public double getPrice() {
return 10;
}
}

抽象装饰类

public abstract class Decorator implements MilkTea {

protected MilkTea milkTea;

public Decorator(MilkTea milkTea) {
this.milkTea = milkTea;
}

@Override
public String getDescription() {
return milkTea.getDescription();
}

@Override
public double getPrice() {
return milkTea.getPrice(); // 价格由种类来决定
}
}

具体装饰类:给奶茶加入椰果

public class Coconut extends Decorator {

private String description = "加了椰果!";

public Coconut(MilkTea milkTea) {
super(milkTea);
}

@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}

@Override
public double getPrice() {
return milkTea.getPrice() + 3; // 3 表示椰果的价格
}

}

具体装饰类:给奶茶加入布丁

public class Pudding extends Decorator {

private String description = "加了布丁!";

public Pudding(MilkTea milkTea) {
super(milkTea);
}

@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}

@Override
public double getPrice() {
return milkTea.getPrice() + 5; // 5表示布丁的价格
}
}

具体装饰类:给奶茶加入珍珠

public class Pearl extends Decorator {

private String description = "加了珍珠!";

public Pearl(MilkTea milkTea) {
super(milkTea);
}

@Override
public String getDescription() {
return milkTea.getDescription() + "\n" + description;
}

@Override
public double getPrice() {
return milkTea.getPrice() + 10; // 10表示珍珠的价格
}
}

测试

// 第一种写法
public class Test {
public static void main(String[] args) {
// 选择巧克力奶茶
MilkTea milkTea = new ChocolateMT();
// 第一次修饰,为巧克力奶茶添加布丁
milkTea= new Pudding(milkTea);
// 第二次修饰,为巧克力奶茶添加椰果
milkTea = new Coconut(milkTea);
System.out.println(milkTea.getDescription() + "\n加了布丁的巧克力奶茶的价格:" + milkTea.getPrice());
}
}

// 第二种写法
public class Test {
public static void main(String[] args) {
// 选择巧克力奶茶
MilkTea milkTea = new ChocolateMT();
// 第一次修饰,为巧克力奶茶添加布丁
Pudding puddingMilkTea = new Pudding(milkTea);
// 第二次修饰,为巧克力奶茶添加椰果
Coconut coconutMilkTea = new Coconut(puddingMilkTea);
System.out.println(coconutMilkTea.getDescription() + "\n加了布丁的巧克力奶茶的价格:" + coconutMilkTea.getPrice());
}
}

测试结果:

巧克力奶茶

加了布丁!

加了椰果!

加了布丁的巧克力奶茶的价格:23.0

测试用例的两种写法结果都是一样的

总结

由上面的案例可以看出装饰器模式的代码结构,使用类的组合来代替继承,但其实装饰器模式相对于简单的组合关系,还有两个比较特殊的地方。

第一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。 比如上面测试类中的第二种写法,对巧克力奶茶嵌套了两个装饰器类,给它添加了椰果和布丁。

第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。 实际上,符合“组合关系”这种代码结构的设计模式有很多,比如之前讲过的代理模式、桥接模式,还有现在的装饰器模式。尽管它们的代码结构很相似,但是每种设计模式的意图是不同的。就拿比较相似的代理模式和装饰器模式来说吧,代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。

在 Java 中的 IO 类库是使用装饰器模式的经典场景。

设计模式——装饰器模式_ide_02

InputStream in = new FileImputStream("/user/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();