先看这次代码示例的需求环境:
我们有一个咖啡店,出售各种样式的咖啡,比如House Blend ,Espresso... ,每种咖啡可以添加不同的作料,比如Mocha,Soy....。我们希望用户下单的时候,可以告诉他们购买的咖啡类型,添加的作料,以及总计价格。
还是这样,我们先看一下思路:
首先肯定要有一个基类。我们命名为Beverage;里面有一个description属性,用来描述咖啡类型以及作料种类、getset方法是必须的。还应该有cost方法,用来计算所有商品的价格,其他还有很多方法,我们这里先不考虑。大致类图是这样的:
当我们想在一种咖啡下添加作料时,应该怎么做。
每一种咖啡配N种作料,所以每一种情况建一种类肯定是不可取的。所以我们第一想法是可以在咖啡类中添加变量,用来控制添加的作料。像这样:
这样就可以实现我们的功能,而且显然比每种情况建一种类要“高明”的多。
肯定会存在问题,要不然装饰模式从何引出,是不是,那问题在那里?
1.加入我们新进了一种新的作料,我们是不是只能修改超类的代码?
2.有一些咖啡类型,并不适用于添加所有作料,我们怎样控制,让一些咖啡类型无法添加某些作料?
3.如果顾客想要加双倍的摩卡(Mocha),这种设计方式能实现么?
细细思考之后就发现,这些问题我们现在的程序很难解决。或者即使解决,也有很多不便。程序设计的另一个原则告诉我们这种编程方式是不可取的:
类应该对扩展开放,对修改关闭
这个设计原则的意思是,当有新的部分要添加的时候,我们应该要做到随时可以简单的扩展;而每次增删部分的时候,我们不希望它将我们之前写好的,没有bug的代码做硬性修改,因为我们无法确定是不是修改之后会出现其他bug。
那么问题来了,怎样才能做到在不硬性修改原有代码的情况下,对现有类进行扩展呢?
装饰者模式,便是解决这个问题的好例子。它完全遵循开放-关闭原则。
我们先来简单了解一下装饰者模式在我们这个例子上的思路:
1.我们先要有一个咖啡对象,比如Espresso类。
2.用摩卡(Mocha)对象装饰它
3.用豆浆(soy)装饰它。
4.调用cost(),并依赖委托,将调料的价钱加到咖啡上。
这四步主要有两个问题,怎样用作料装饰咖啡? 怎样依赖委托将价格加上去呢?
看个图:
我们建立一个Mocha作料对象,并将Espresso对象包起来。如果想要再添加Soy,我们就建立一个Soy对象,将Mocha包起来。也就是说,Mocha以及Soy包起来之后的对象仍然是一个Beverage,他们仍具有Espresso对象的一切属性和方法。比如他们的cost()方法。
现在我们能知道些什么呢?
1.装饰者和被装饰者有着相同的超类。
2.可以用一个或者多个装饰者包装一个对象。
3.因为第一点,所以在任何可以使用被装饰者对象的地方,你都可以用带有作料的装饰者替代它。
4.之所以叫装饰者,是因为可以再委托被装饰者做一些行为之前,先做一些自己的行为,比如改变被装饰着里的一些属性。
5.对象可以在任何时候被装饰,所以我们可以再运行时动态的、无限的装饰被装饰着(给咖啡任意的添加作料)。
如果你对上面的五条还不是很了解,看看下面的实现代码,可能会有感悟。
超类:
public abstract class Beverage { //定义一个属性,用来描述当前物品的状态(什么咖啡,带有什么作料) String description="Unknown Beverage"; public String getDescription(){ return description; } //抽象的cost,子类自己实现 public abstract double cost(); }
装饰者基类:
//因为之前说了,装饰者必须和被装饰者有着相同的超类,所以同样继承Beverage public abstract class CondimentDecorator extends Beverage{ public abstract String getDescription(); }
一些咖啡子类:
public class Espresso extends Beverage { //在构造器中改变description, public Espresso(){ description="Espresso "; } //重写cost,加上自己的价钱。 @Override public double cost() { return 1.99; } }
public class HouseBlend extends Beverage { public HouseBlend(){ description="HouseBlend "; } @Override public double cost() { return 0.89; } }
一些作料子类:
public class Mocha extends CondimentDecorator { //装饰者要有一个被装饰着变量,这样才能委托给他,调用一些方法。 Beverage beverage; public Mocha(Beverage beverage){ this.beverage=beverage; } //和咖啡子类相同,在构造器中,添加自己到描述中 @Override public String getDescription() { return beverage.getDescription()+",Mocha"; } //将自己的价钱添加到被装饰者的总价钱中 @Override public double cost() { return beverage.cost()+0.20; } }
public class Soy extends CondimentDecorator { Beverage beverage; public Soy(Beverage beverage) { this.beverage=beverage; } @Override public String getDescription() { return beverage.getDescription()+",Soy"; } @Override public double cost() { return beverage.cost()+0.10; } }我们来模拟一下用户点单:
public static void main(String[] args) { //点一杯咖啡,不加任何作料 Beverage beverage1=new HouseBlend(); System.out.println(beverage1.getDescription()+" $ "+beverage1.cost()); Beverage beverage=new Espresso(); //添加点摩卡调料 beverage=new Mocha(beverage); //再添加一点豆浆 beverage=new Soy(beverage); System.out.println(beverage.getDescription()+" $"+beverage.cost()); }结果:
HouseBlend $ 0.89
Espresso ,Mocha,Soy $2.29
双倍摩卡?
Beverage beverage=new Espresso(); //添加点摩卡调料 beverage=new Mocha(beverage); //再添加一点豆浆 beverage=new Soy(beverage); //双倍摩卡 beverage=new Mocha(beverage); System.out.println(beverage.getDescription()+" $"+beverage.cost());HouseBlend $ 0.89
Espresso ,Mocha,Soy,Mocha $2.49
你也可以在输出前做一些判断,如果有了mocha,将mocha变为double Mocha,或者可以把getDescription返回值改为List,相信不难。
再来看看装饰者模式的一般定义,可能会更有体会:
不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的解决方案。
java内置的类中,有很多装饰者模式的例子,其中以InputStream以及它的扩展类最为典型,比如FileInputStream,StringBufferInputStream..
都是InputStream的扩展类。对其进行了包装。有兴趣的可以看看。
好了,装饰模式,就聊这么多。