装饰者模式指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。就好比一个姑娘化妆,化完妆之后其实还是本人,只不过是多了一些功能,变好看了。因此,初学者可以简单理解成,装饰者模式就是外界传入一个对象(可以是构造方法也可以是set方法传入对象),对这个对象进行一些次要功能增强,然后返回的还是这个对象。这一点和代理模式有点点像,这里先不做区分。我们先来看一个快餐店的例子。快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。如果使用继承实现,类图如下:
使用继承的方式存在的问题:
扩展性不好:如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice
和FriedNoodles
分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
产生过多的子类
装饰(Decorator)模式中的角色:
我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。类图如下:
我们先定义各个实体类,如下:
// 定义快餐抽象类
public abstract class FastFood {
private Float price;
private String name;
public FastFood() {
}
public FastFood(float price, String name) {
this.price = price;
this.name = name;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 炒饭类,继承快餐类
public class FriedRice extends FastFood{
public FriedRice(){
super(10,"炒饭");
}
}
// 炒面类,继承快餐类
public class FriedNoodles extends FastFood{
public FriedNoodles(){
super(13,"炒面");
}
}
为了理解先给出客户端测试类 (Egg和Bacon还没有实现,可以先思考一下如何实现),即现在我们想要的效果!这里也是理解装饰器模式的一个重要的方式,我们想要一个实现类,传入一个需要被装饰的对象,得到一个装饰好了的对象,如下:
public class Main {
public static void main(String[] args) {
// 点一份炒饭 10元
FriedRice friedRice = new FriedRice();
System.out.println(friedRice.getName()+":价格"+friedRice.getPrice()+"元");
// 加蛋 加2元
Egg eggFriedRice = new Egg(friedRice);
System.out.println(eggFriedRice.getName()+":价格"+eggFriedRice.getPrice()+"元");
// 点个培根炒面 13+3=16元
Bacon baconFriedRice = new Bacon(new FriedNoodles());
System.out.println(baconFriedRice.getName()+":价格"+baconFriedRice.getPrice()+"元");
}
}
输出:
炒饭:价格10.0元
鸡蛋炒饭:价格12.0元
培根炒面:价格16.0元
为了实现上述效果,我们使用装饰器模式,定义一个装饰器类继承FastFood
类并持有一个FastFood
对象,这里解释为什么:继承FastFood
于是我们的装饰器类本身就是FastFood
类,类本身代表的是装饰的内容(鸡蛋对象),而持有的FastFood
对象代表的是需要装饰的对象(传进来的炒饭对象)。炒饭是FastFood
类,鸡蛋也是FastFood
类,那么蛋炒饭就是装饰器装饰出来的对象。代码如下:
// 装饰器类
public abstract class Garnish extends FastFood {
private FastFood fastFood; // 需要装饰的对象
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood,float price,String name){
super(price,name); // 对象本身是附加的配菜,例如 蛋、培根等
setFastFood(fastFood); // 设置需要修饰的对象(炒饭、炒面)
}
}
// 鸡蛋装饰器,对传进来的对象装饰一个鸡蛋
public class Egg extends Garnish{
public Egg(FastFood fastFood){
super(fastFood,2,"鸡蛋"); // 加蛋两元
}
public Float getPrice() {
// 需要装饰对象的价格(炒饭或者炒面的价格)+装饰的价格(配菜蛋的价格)
return this.getFastFood().getPrice()+super.getPrice();
}
@Override
public String getName(){
return super.getName()+this.getFastFood().getName();
}
}
// 培根装饰器:对传进来的对象装饰一个培根
public class Bacon extends Garnish{
public Bacon(FastFood fastFood) {
super(fastFood, 3, "培根");
}
@Override
public Float getPrice() {
// 需要装饰对象的价格(炒饭或者炒面的价格)+装饰的价格(配菜培根的价格)
return this.getFastFood().getPrice()+super.getPrice();
}
@Override
public String getName(){
return super.getName()+this.getFastFood().getName();
}
}
这里的装饰器其实持有了两个FastFood
对象,一个是自身继承了FastFood
,另外靠一个成员变量持有一个FastFood
对象,其中一个对象是被装饰对象,另外一个对象是装饰品。思路是这样的,但是换一个情景,可能又不是这样设计了,但是值得注意一点就是装饰器就是接受一个需要装饰的对象,然后给他装饰,装饰完了之后得到新的装饰者对象即可。
饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
参考内容:
传智播客系列设计模式笔记(主要)
https://zhuanlan.zhihu.com/p/444298983