作者:过客松鼠_230 | 来源:互联网 | 2023-09-08 10:18
一、定义
装饰者模式是一种比较常见的模式,其定义如下:
动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。
装饰者模式通用类图如下:
![](https://img0.php1.cn/3cdc5/6d68/78c/03c1773991c14917.png)
二、角色分析
装饰者模式有四个角色需要说明:
抽象构建类,可以是抽象类或者接口,定义了被装饰者类的一些抽象方法等,即最基本的功能。
具体构建类,实现或者继承了抽象构建类,实现了具体的方法,真正被装饰的其实是它。
一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,在它的属性里面必然有一个private变量指向Component抽象构件。
真正在这里扩展原始对象的功能,针对不同的具体构建类,可以定义多个具体装饰角色,例如ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类。
三、装饰者模式案例分析
下面我们以一个日常生活中常见的例子来说明装饰者模式的使用。
冬天快到了,很多小伙伴都会喜欢打火锅,打火锅肯定要有一个汤底,然后我们可能还会点各种各样的配菜,在增加配菜的同时,我们需要动态计算吃这一顿的总价钱。
接下来我们就使用装饰者模式来把上面这种场景进行抽象。
首先,UML类图如下:
![](https://img0.php1.cn/3cdc5/6d68/78c/f1dbf16d8030219c.png)
我们首先定义抽象构件角色,主要提供两个方法:获取价格的方法getPrice()、获取描述信息的方法getDesc(),代码如下:
/*** 抽象构件: 火锅类*/
public abstract class HotPotComponent {/*** 获取价格** @return*/protected abstract BigDecimal getPrice();/*** 获取描述信息** @return*/protected abstract String getDesc();
}
接下来定义一个具体构件角色:本例中就是火锅底料对象,真正装饰的也就是它。
/*** 具体构件类: 火锅底料*/
public class HotPotSeasoningComponent extends HotPotComponent {@Overrideprotected BigDecimal getPrice() {return BigDecimal.valueOf(20);}@Overrideprotected String getDesc() {return "火锅底料";}
}
接着,定义一个抽象装饰者类,它继承自抽象构件角色,内存持有一个指向抽象构件角色的引用:
/*** 抽象装饰者类*/
public abstract class AbstractDecorator extends HotPotComponent {private HotPotComponent hotPotComponent;public AbstractDecorator(HotPotComponent hotPotComponent) {this.hotPotComponent = hotPotComponent;}@Overrideprotected BigDecimal getPrice() {return hotPotComponent.getPrice().add(getSideDishPrice());}@Overrideprotected String getDesc() {return hotPotComponent.getDesc() + " + " + getSideDishDesc();}/*** 获取配菜的描述** @return*/protected abstract String getSideDishDesc();/*** 获取配菜的价格** @return*/protected abstract BigDecimal getSideDishPrice();}
可以看到, 除了实现抽象构件的抽象方法之外,抽象装饰者角色内存还定义了配菜的一些方法。并且重写了抽象构件的getPrice()方法和getDesc()方法,动态的计算所有配菜的总价格。
有了抽象装饰者角色,就需要定义具体的装饰者角色进行增强了:
/*** 具体装饰者角色: 肥牛*/
public class FatCattleDecorator extends AbstractDecorator {public FatCattleDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "肥牛";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(20);}
}/*** 具体装饰者角色: 小白菜*/
public class CabbageDecorator extends AbstractDecorator {public CabbageDecorator(HotPotComponent hotPotComponent) {super(hotPotComponent);}@Overrideprotected String getSideDishDesc() {return "小白菜";}@Overrideprotected BigDecimal getSideDishPrice() {return BigDecimal.valueOf(10);}}
最后,我们定义一个场景类进行测试:
public class Client {public static void main(String[] args) {//层层包装HotPotComponent hotPotComponent = new HotPotSeasoningComponent();//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份肥牛hotPotComponent = new FatCattleDecorator(hotPotComponent);//一份小白菜hotPotComponent = new CabbageDecorator(hotPotComponent);System.out.println(hotPotComponent.getDesc() + " = " + hotPotComponent.getPrice() + "元");}
}
运行结果:
火锅底料 + 肥牛 + 肥牛 + 小白菜 = 70元
可见,成功扩展原有对象的功能,这样我们以后如果需要扩展火锅底料的功能,只需要增加一个具体的装饰者角色即可实现扩展,而不需要改动原有的代码,这符合开闭原则。
四、装饰者应用分析
装饰者模式在我们的IO流中体现的最为显著,下面我们看一下IO流中InputStream相关的UML类图:
![](https://img0.php1.cn/3cdc5/6d68/78c/863fdbe2500a450f.png)
对应前面的通用类图应该很容易看出各个角色分别是谁:
- OutputStream和InputStream就对应于抽象构件角色(Component);
- FileInputStream和FileOutputStream就对应具体构件角色(ConcreteComponent);
- FilterOutputStream和FilterInputStream就对应着抽象装饰角色(Decorator);
- BufferedOutputStream,DataOutputStream等等就对应着具体装饰角色;
下面是关键源码:
//抽象类,抽象构建角色
public abstract class InputStream implements Closeable {//省略...
}//具体构建角色,也是具体被装饰的类
public class FileInputStream extends InputStream{//省略...
}//装饰者基类,该类持有一个抽象构建角色InputStream的引用
public class FilterInputStream extends InputStream {/*** The input stream to be filtered.*/protected volatile InputStream in;//省略...
}//具体装饰者角色
public class BufferedInputStream extends FilterInputStream {//省略...
}
五、总结
装饰者模式的优点:
- 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
- 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
- 装饰者模式可以动态地扩展一个实现类的功能;
装饰者模式的缺点:
- 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。
装饰者模式的使用场景:
- 需要动态扩展一个类的功能,或给一个类在增加附加功能;
- 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
- 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;