装饰器模式(从放弃到入门)
@(设计模式)
前面介绍了两篇设计模式,策略模式和观察者模式,其实自己也是在学习阶段,感觉收益很大。所以想继续分享,把java中的23中设计模式都总结一遍,在以后才能在实践中灵活运用。感兴趣的童鞋可以看看前面分享的两篇:
策略模式
观察者模式
前面两篇都是上来就是例子,需求,我想改变一下套路,今天先介绍装饰器的理论结构,再说例子。还是要再声明:例子来自于《HeadFirst 设计模式》,推荐大家看看原书,写得很浅显易懂。
理论知识
实际问题
今天的例子是一个 coffe 的例子,相信大家都去星巴克喝过咖啡,或者奶茶,我们在买coffe的时候,首先选择一个基本的coffe类型,比如 卡布奇洛,然后添加各种佐料:摩卡,豆浆,蒸奶等。基本类型是基础价,佐料又要宁外算钱。(真是聪明)
现在当前的系统设计是:
本来想自己画UML图,发现自己画也一样,还损失了书上一些重要信息,所以直接盗图好了,大家不要介意。
Beverage: 饮料,抽象类,getDescription() 返回描述(类型,佐料…),cost() 抽象方法,每种饮料话费不一样,所以子类自己去实现。
然后派生了4中饮料的子类: HoseBlend, DarkRoast, Decaf, Espresso(尼玛,我都没喝过),分别实现 cost方法,返回价格。
代码很简单,这里就不贴了,然后这个时候,如果饮料有100种,呵呵,这种设计类图就是这样:
类爆炸!这种设计,明显重用率太低,好吧,换种思路,我们把所有的佐料的放到公共父类中,让所有子类都拥有所有的佐料,只是在类中判断到底加没加,例如这样:
代码:
Beverage .java
public abstract class Beverage {
protected String description;
protected boolean milk;
protected boolean soy;
protected boolean mocha;
protected boolean whip;
public Beverage(){
this.description = "unknown beverage";
}
public String getDescription() {
return description;
}
public abstract double cost();
}
HoseBlend.java
public class HoseBlend extends Beverage {
public HoseBlend() {
description = "HoseBlend";
milk = true;
soy = false;
mocha = false;
whip = false;
}
public double cost() {
double money = 10.0;
if (milk)
money += 2.0;
if (soy)
money += 3.0;
if (mocha)
money += 3.0;
if (whip)
money += 2.0;
return money;
}
}
使用:
Beverage hoseBlend = new HoseBlend();
System.out.println(hoseBlend.getDescription());
System.out.println(hoseBlend.cost());
其他类省略了,当然这里写得不规范,每种佐料的价格应该写成常量,这里直接用了数字,这里不是重点。这样的类设计出来,我们可以用一段话来描述:一个抽象的Beverage类,里面包含了很多佐料,子类决定是否添加这些佐料,并且计算初始价格和佐料价格。
与前一种不同的是,这种更加规范,父类决定了所有的佐料和价格,只是你填不填加,自己决定。突然想到了一种更好的方法,为何不让Beverage去计算价格呢?
Beverage.java
public abstract class Beverage {
protected String description;
protected double money;
public Beverage(){
this.description = "unknown beverage";
this.money = 10.0;
}
public String getDescription() {
return description;
}
public double cost(){
return money;
}
public void addMilk() {
this.money += 2.0;
}
public void addSoy(){
this.money += 3.0;
}
public void addMocha(){
this.money += 3.0;
}
public void addWhip(){
this.money += 2.0;
}
}
添加4中 addXXX() 方法,然后计算money,将计算价格留给父类,子类只需要添加佐料:
HoseBlend .java
public class HoseBlend extends Beverage {
public HoseBlend() {
description = "HoseBlend";
addMilk();
}
}
感觉这样写重用可以更好,并且子类工作也更少了。呵呵,书上没写,自己瞎想的。但是上面的这种设计,当遇到添加一种佐料时,都必须修改Beverate类,在设计模式原则中有一个非常重要的原则,就是开闭原则:对扩展开放,对修改关闭。上面的几种设计都存在一定的问题。我们来看看今天的主角,装饰器模式,怎么来完成。
装饰器模式
先来看一张形象的图:
最里层的 DarkRoast 是饮料类型外面添加一层 Mocha, 再外层添加 Whip。最终cost() 就从最外层,一直调用到最里层,累加得到价格,呵呵,是不是有点像递归,对多态的递归,看看类结构:
当然这是在最初,我们类爆炸那个例子中的结构扩展的:
Beverage依然是那个抽象类,依然有4种饮料继承与它。不同的是,多了一个 CondimentDecorator,继承于 Beverage,然后4种佐料都继承与 CondimentDecorator,并都包含一个 beverage 的引用,表示自己装饰的对象。
好了,看看代码:
Beverage .java 还是长这样
public abstract class Beverage {
protected String description;
public Beverage(){
this.description = "unknown beverage";
}
public String getDescription() {
return description;
}
public abstract double cost();
}
HouseBlend .java 第一种饮料,最低10.0元
public class HouseBlend extends Beverage {
public HouseBlend(){
description = "HoseBlend";
}
public double cost() {
return 10.0;
}
}
DarkRoast.java 第二种饮料,最低12.0元
public class DarkRoast extends Beverage {
public DarkRoast(){
description = "DarkRoast";
}
public double cost() {
return 12.0;
}
}
CondimentDecorator .java 让子类都重写getDescription() 方法
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
Milk.java : 牛奶,每加一份2.0 元
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return this.beverage.getDescription() + "," + "Milk";
}
public double cost() {
return this.beverage.cost() + 2.0;
}
}
Mocha.java 摩卡,每份3.0元
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return this.beverage.getDescription() + "," + "Mocha";
}
public double cost() {
return this.beverage.cost() + 3.0;
}
}
Soy.java 酱油,每份3.0元
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return this.beverage.getDescription() + "," + "Soy";
}
public double cost() {
return this.beverage.cost() + 3.0;
}
}
好了,看看我们怎么调用:
public class Main {
public static void main(String[] args) {
Beverage b1 = new HouseBlend();
System.out.println(b1.getDescription() + " $" + b1.cost());
Beverage b2 = new DarkRoast();
b2 = new Mocha(b2);
b2 = new Soy(b2);
b2 = new Milk(b2);
System.out.println(b2.getDescription() + " $" + b2.cost());
}
}
输出:
HoseBlend $10.0
DarkRoast,Mocha,Soy,Milk $20.0
总结
- 解耦合了吧,饮料和佐料分开,想怎么加怎么加,如果要添加新饮料或者佐料,只需继续添加,而无需修改以前的结构。这就是对扩展开放,对修改关闭。
- 我前面提到了一句话:递归多态,哈哈哈,自己瞎编的!为什么会出现这种结果,如果你对多态熟悉的话,就很好理解了,看上面代码的 b2:
- 为什么 Milk 可以赋值给 Beverage , 因为 Milk 的父类继承于 Beverage
- b2调用 cost() 是调用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一个b2,及 Soy, Soy调用cost(), 及调用 Mocha.cost() + 3.0 … , 知道最后调用 beverage 的 cost() , 是不是很像递归。
再一句话概括装饰者模式吧:
装饰者模式,就是装饰者(Docorator)需要继承与被装饰者(Component),并且持有被装饰者的引用(Component),从而可以通过复写,在原来 Component 的基础上对方法做一定的修饰。