本文示例代码材料源自Head First设计模式
以前整理自己整理的装饰模式的链接:https://blog.csdn.net/u011109881/article/details/58719049
思想
最大特点就是不修改原有代码的前提下,可以新增功能,使用情况可以参见我之前的总结,时隔一年,发现原文(http://www.runoob.com/design-pattern/design-pattern-intro.html)在这个模式总结的还是非常好的。
在之前的总结,使用的例子是画形状和着色问题,画形状是一个基本功能,着色的功能可以通过装饰器模式给原来的画图形装饰一下,那么画图形的工具也具有了着色的功能。
这里我还是采用Head First中饮料和调料的例子来模拟装饰模式。初学阶段,多看一个例子,能够看出一些通用的规则,不是么。
上栗子:
当前有个需求如下:一家奶茶店出售各式饮料,比如饮料A,饮料B,饮料可以放入不同的配料,比如加冰,加巧克力,加摩卡等等。要如何实现呢。是否可以通过继承来实现?比如实现巧克力饮料A,摩卡饮料B,加冰饮料A。。。。。。可以想见,这样会出现类爆炸的情况,如果客人需要双倍加冰双倍摩卡饮料A怎么办?如果后期饮料或是配料增加了种类,又会增加一大波类出来。
那么,如果在饮料中添加各个配料标志位来表示是否需要添加该配料的情况怎么样?这样乍看很合适,但是当增加配料的时候,如果饮料的种类很多,那么对原来设计的修改就比较麻烦了。
就没有一种情况,可以在不修改原来的代码,并且还能在饮料和配料增加的情况还能简便的扩展的方法么?那就用到今天的主角装饰模式了。
装饰模式的核心在于装饰类和被装饰类具有共同的基类。这样,装饰者就可以在原来的基类上包装一下,看上去多了一些功能或操作。
示例思路(规划类图)
装饰模式基本UML图:
如果运用到饮料和配料的例子中,UML图就是这样的:
实际代码
被装饰者–饮料类
public interface IBeverage {
public String getDescription();
public double getCost();
}
public class BeverageA implements IBeverage{
String description = " BeverageA ";
double cost = 1.9;
public String getDescription() {
// TODO Auto-generated method stub
return description;
}
public double getCost() {
// TODO Auto-generated method stub
return cost;
}
}
public class BeverageB implements IBeverage{
String description = " BeverageB ";
double cost = 2.1;
public String getDescription() {
// TODO Auto-generated method stub
return description;
}
public double getCost() {
// TODO Auto-generated method stub
return cost;
}
}
装饰者–配料类
public interface ICondimentDecorator extends IBeverage{
}
public class Condiment1 implements ICondimentDecorator{
String description = " Condiment1 ";
double cost = 0.1;
IBeverage beverage;
public Condiment1(IBeverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
// TODO Auto-generated method stub
return description + beverage.getDescription();
}
public double getCost() {
// TODO Auto-generated method stub
return cost+beverage.getCost();
}
}
public class Condiment2 implements ICondimentDecorator{
String description = " Condiment2 ";
double cost = 0.2;
IBeverage beverage;
public Condiment2(IBeverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
// TODO Auto-generated method stub
return description + beverage.getDescription();
}
public double getCost() {
// TODO Auto-generated method stub
return cost+beverage.getCost();
}
public void mthodOf2(){
System.out.println("this is meyhod of 2");
}
}
就贴2个吧,第三个和前面的很像
测试类
public class testMain {
public static void main(String[] args) {
IBeverage a = new BeverageA();
System.out.println(a.getCost());
System.out.println(a.getDescription());
a = new Condiment1(a);
System.out.println(a.getCost());
System.out.println(a.getDescription());
a = new Condiment2(a);
System.out.println(a.getCost());
System.out.println(a.getDescription());
a = new Condiment3(a);
System.out.println(a.getCost());
System.out.println(a.getDescription());
Condiment3 aaa = (Condiment3)a;
aaa.mthodOf3();
}
}
Condiment2和Condiment3中我另外添加了mthodOf方法,用于测试装饰模式的一些不够灵活的地方,我后面说明
测试结果:
1.9
BeverageA
2.0
Condiment1 BeverageA
2.2
Condiment2 Condiment1 BeverageA
2.5
Condiment3 Condiment2 Condiment1 BeverageA
this is meyhod of 3
总结
现在,我们回过头来看,不修改原有代码的前提下,可以新增功能么?
当然可以,如果新加了饮料,那么我们就新加一个Beverage的实现类;如果新加一个配料,那么我们就新加一个Condiment实现类;要添加各种配料,只需要拿到IBeverage的实现类实例再包装一下即可,而且最神奇的地方在于原来的结构完全没有修改。
装饰模式高度契合对修改关闭对扩展。然而装饰模式也有它的缺点。
首先 ,装饰模式对类型的控制很重要,比如在被装饰类饮料中有个方法,但是使用装饰类配料去包装之后,所得到的已经不再是饮料的引用了,它已经变成配料的引用了,此时如果调用被装饰者饮料的方法是不能调用的,强制类型转换也无法实现。因此,装饰模式最后一个包装的类型决定了它能调用什么方法。
另外,装饰器模式中装饰者和被装饰者之间的关系界限不是很明显,经过一通包装之后,我们只能看到最外面的一层对象,至于里面的核心是什么对象,一共包裹了几层包装,比较难看出来。前面我在Condiment2和Condiment3中另外添加了mthodOf方法,如果最后包装的是Condiment3,那么我只能调用到mthodOf3方法,至于里面一层的包装的方法,是无法调用到的。
最后,如果没有清晰的理解装饰者模式,这种复杂的层层包装的模式让人很费解。比如JAVA API中的IO流就是最经典的装饰模式,当时学习时也感觉云里雾里的,现在才算拨开云雾见青山呢。
是不是和饮料及配料的结构很像呢?