目录
引言
装饰模式。
顾名思义,装饰是什么?就是对某个东西进行修饰,从而使得某个东西具有更多的特性。
对应在java中,装饰的东西常常是一个类,对其进行装饰的目的是为了让它具有更多的属性和方法。然而实现这个目的却需要一种合理的方式。一般的思路是什么呢?
假设有一个接口Plant,该接口有一个实现类Tree。普通的树结的果子就是果子,但是有可能有些树结的果子是金子(。。。),比如摇钱树。实现树结出金子的方案有以下3种:
1、修改实现类Tree的方法。直接修改方法,突出一个不是树木共性的特性,这样不是很好;
2、再增加一个实现类,实现摇钱树。这样的话如果我想生成具有更多特性的树,岂不是需要生成很多实现类?
3、装饰模式,使用一个抽象类横向地扩展想要修饰的类(称为目标类)。这样不会影响目的类继承链上的其他类,例如:C extends B , B extends A,如果需要扩展B的功能,可以设计一个B的装饰类,它并不会影响B的子类C。如果采用在B里面增加方法,势必会使B的所有子类结构被改变。。
示例
装饰模式的一般步骤:
- 定义一个接口Plant;
- 定义一个装饰目标类Tree,实现接口Plant;
- 定义一个用来装饰的横向扩展类(抽象类)TreeDecorator,内部持有目标类Tree的引用;
- 定义一个横向扩展类的实现类TreeDecorator ,增强目标类想要增强的方法bearFruit();
- 最后,客户端可以通过调用横向扩展类的实现类,来实现对Tree结出金子的方法,并且保证了不会影响Tree类的继承链上的其他类。
代码
接口:
public interface Plant {
// 定义一个结果的方法
String bearFruit();
}
实现类Tree(目标类):
public class Tree implements Plant {
// 针对这个方法,想对他进行加强
@Override
public String bearFruit() {
return "树结果子";
}
}
横向扩展的抽象类(装饰类):
public class AbstractDecorator implements Plant {
// 持有目标类的引用
private Plant plant;
// 构造函数注入目标类
// 注:模块间的依赖通过抽象的接口(Plant)产生,而不是通过实现类(Tree),符合依赖倒置原则
public AbstractDecorator (Plant plant) {
this.plant = plant;
}
// 调用目标类的方法
@Override
public String bearFruit(){
return plant.bearFruit();
}
}
值得注意的是:模块间的依赖通过抽象的接口(Plant)产生,而不是通过实现类(Tree),符合依赖倒置原则
装饰类的实现类:
public class TreeDecorator extends AbstractDecorator {
public TreeDecorator(Plant plant) {
super(plant);
}
//装饰类增加的功能
private String money() {
return "这是摇钱树,结金子!";
}
//增强了功能的bearFruit方法
@Override
public String bearFruit() {
return super.bearFruit() + "," + money();
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
// 创建目标类
Plant plant = new Tree();
// 创建装饰类,添加目标类的引用
Plant moneyTree = new TreeDecorator(plant);
// 执行增强后的结果方法,bearFruit()
System.out.println(moneyTree.bearFruit());
}
}
运行结果:
树结果子,这是摇钱树,结金子!
总结
使用场景:
- 替代继承,扩展一个类的功能
- 动态的给一个对象添加功能,以及动态的撤销该功能
优点:
- 动态扩展一个实现类的功能,在不需要添加功能的时候,可以撤销装饰。
- 装饰类和被装饰类模块间,通过抽象产生依赖,不会相互耦合
- 装饰模式替换继承,可以避免继承链的子类被影响
装饰模式与代理模式的区别
- 装饰模式:侧重给一个实现类动态添加功能,不会对实现类的方法进行过滤拦截
- 代理模式:侧重将一个实现类的功能,委托给代理类来处理,可以对实现类的方法进行过滤拦截(某种情况下,可能不执行实现类的方法)