- 装饰者模式简介
-
案例:星巴克
- 1、小白设计:单纯继承
- 2、使用装饰者模式
- 3、jdk的I/O框架
- 4、案例总结
装饰者模式简介
定义:装饰者模式动态的将责任附加到对象上,若要拓展功能,装饰者提供了比继承更有弹性的方案,装饰者可以在被装饰者行为前后加上自己的行为以达到特定的目的。
从UML图上理解呢,就是有一个抽象的组件,定义了抽象的方法,由于拓展的需要,抽象了一个装饰类,继承这个类的所有类都是装饰了原有组件之后的组件。这里需要注意的是,Decorator和Component是组合关系,脱离了Component就不复存在了,说到底,被拓展的类还是原来的组件,只不过增加了一些东西。由于组合的依赖甚,装饰类他们本身是可以代表组件本身的。原则就是在原有的基础上增加新的东西,无论是功能还是属性都可以,但是无法变成脱离原来东西以外的新东西。
案例:星巴克
需求:本店主营是做各种咖啡以及其他饮料的。不管怎样,这家都都是做饮料的。客人越来越多了,现在我们要用开发一个收款的程序,来管理所有的品类,代替人工计算商品价格,方便店员收款。
1、小白设计:单纯继承
思考:既然都是饮料,那么抽象一个饮料父类。以后每个单品继承该父类,并再子类计算价格即可。于是设计的如下。
这个设计满足了一段时间的使用,但是问题来了,由于客人越来越多,店面也有扩张,从原来的4个单品扩张为100个单品,并且还有提供了各种调味料调味,加糖的,加盐的,加奶的等等。突然发现这种组合太恐怖了,那我要成百上千的类啊。而且随着后续组合的增加,每增加一个品种,其组合都是爆炸式的增长。而且这还不是最麻烦的,计算价钱怎么办?得每一种组合里面自己算。可是调味料和品类就这么多。而我要算很多次。太恐怖了。
总结:类爆炸,单纯的继承对于组合拓展来说,简直式噩梦,会导致类爆炸。
2、使用装饰者模式
思考:思考一下需求,饮料需要加调味料,饮料的种类和调味料的种类都式可以拓展的。我们之前学习的策略模式和观察者模式好像都不太够用啊。如果用策略模式来做,那么每一种组合就是一种策略,也会有很多类。能参考的只有之前学的设计原则,多用组合了,这个需求本身就是组合,查阅资料,发现装饰者模式,激动万分。改造一下。
饮料的四种单品:DarkRoast、Decaf、Espresso、HouseBlend 都是咖啡
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:00.
*
* 饮料
*/
public abstract class Beverage {
protected String discription = "unknown beverage";
public String getDiscription() {
return discription;
}
/**
* 计算价格
* @return
*/
public abstract double cost();
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:03.
*
* 单品:DarkRoast
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
discription = "DarkRoast";
}
@Override
public double cost() {
return .99;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:03.
*
* 单品:Decaf
*/
public class Decaf extends Beverage {
public Decaf() {
discription = "Decaf";
}
@Override
public double cost() {
return 2.33;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:03.
*
* 单品:Espresso
*/
public class Espresso extends Beverage {
public Espresso() {
discription = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:03.
*
* 单品:HouseBlend
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
discription = "HouseBlend";
}
@Override
public double cost() {
return .89;
}
}
调料的四种种类:Milk、Mocha、Soy、Whip
备注:个人理解,这里定义的其实是用调料装饰单品后的东西。因此以下的类并不是单纯指调料。只是我们使用的时候方便理解这么命名了。
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:04.
* <p>
* 调料装饰者
*/
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
@Override
public abstract String getDiscription();
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:06.
*
* 被Milk装饰
*/
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription() + ", Milk";
}
@Override
public double cost() {
return beverage.cost() + .23;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:06.
*
* 被Mocha装饰
*/
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription() + ", Mocha";
}
@Override
public double cost() {
return beverage.cost() + .20;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:06.
*
* 被Soy装饰
*/
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription() + ", Soy";
}
@Override
public double cost() {
return beverage.cost() + 0.44;
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 16:06.
*
* 被Whip装饰
*/
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDiscription() {
return beverage.getDiscription() + ", Whip";
}
@Override
public double cost() {
return 0.56 + beverage.cost();
}
}
测试一下:
public class Test {
public static void main(String[] args) {
//点一杯Espresso
Beverage espresso = new Espresso();
System.out.println(espresso.getDiscription());
System.out.println(espresso.cost());
//点一杯DarkRoast,加两份Mocha,加一份Whip
Beverage darkRoast = new DarkRoast();
darkRoast = new Mocha(darkRoast);
darkRoast = new Mocha(darkRoast);
darkRoast = new Whip(darkRoast);
System.out.println(darkRoast.getDiscription());
System.out.println(darkRoast.cost());
//来一杯调料为:豆浆,摩卡,奶泡的HouseBlend咖啡
Beverage houseBlend = new HouseBlend();
houseBlend = new Soy(houseBlend);
houseBlend = new Mocha(houseBlend);
houseBlend = new Whip(houseBlend);
System.out.println(houseBlend.getDiscription());
System.out.println(houseBlend.cost());
}
}
总结:小编发现其实还是用的继承,只不过多了一个组合的思想。组合就应该是用组合,对象之间都是组合关系的,我们就没有理由不用组合去设计了。如果不想用组合这里的抽象类也可以写成接口。将依赖下方到具体的子类中其实是一样的。只不过由于组件和装饰者这些都是是一个个角色,使用抽象的意义貌似比接口要大一点。因为基调就是组合衍生新的组合。这么一看,貌似又点熟悉,java的I/O框架是不是也是用的这种模式,一层一层包装。
3、jdk的I/O框架
仔细观察IO框架,其实我们可以发现其设计就是经典的装饰着模式,不同的是到了java8装饰者并不是抽象的而已。其实如果理解了装饰着模式的核心之后,我们发现其核心的思想在于组合,而不是拘泥于抽象接口或者继承。所有的这些只是手段,我们要达到的是弹性组合的效果。就可以了。而装饰者只是一个角色而已。其依赖上层被装装饰的组件。知道了这一点我们甚至可以自己去定制拓展自己的IO组件了。
4、案例总结:
装饰者模式其实蛮好理解的,核心在于理解组合,弹性组合。关键代码是组合属性以及构造方法。