1.什么是装饰者模式?
装饰者模式动态的将责任附加到对象上。 如果要扩展功能,装饰者提供了比继承更有弹性的替代方案。
2.结合需求案例设计理解此模式
四郎咖啡店因为市场反应很好,所以扩张速度非常快,但因为它的扩张速度太快了,其现有的订单系统无法满足市场需求了,所以他们决定更新订单系统,来满足他们的饮料供应需求。
那么如何进行更新呢? 首先需要了解目前的设计现状
2.1 目前设计现状
他们的系统中原来的类的设计是这样的:
源代码如下:
Beverage.java
package DecoratorPattern.first.base;
public abstract class Beverage {
private String description;
public abstract double cost();
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
HouseBlend.java
package DecoratorPattern.first.coffee;
import DecoratorPattern.first.base.Beverage;
/**
* HouseBlend 定价 5元
*/
public class HouseBlend extends Beverage {
@Override
public double cost() {
return 5;
}
}
DarkRoast.java
package DecoratorPattern.first.coffee;
import DecoratorPattern.first.base.Beverage;
/**
* DarkRoast 定价 10元
*/
public class DarkRoast extends Beverage {
@Override
public double cost() {
return 10;
}
}
Decaf.java
package DecoratorPattern.first.coffee;
import DecoratorPattern.first.base.Beverage;
/**
* Decaf 定价 8元
*/
public class Decaf extends Beverage {
@Override
public double cost() {
return 8;
}
}
测试:
package DecoratorPattern.first;
import DecoratorPattern.first.coffee.Decaf;
import DecoratorPattern.first.coffee.HouseBlend;
public class MainTest {
public static void main(String[] args) {
System.out.println("用户A 点一杯HouseBlend 价格为:"+new HouseBlend().cost());
System.out.println("用户B 点一杯 Decaf 价格为:"+ new Decaf().cost());
}
}
2.2 首次需求
现在因为业务扩张,除了可以购买不同种类的咖啡外,还可以要求在其中加入各种调料,例如 牛奶 Milk, 豆浆 Soy,
摩卡 Mocha等等。 并且咖啡店需要根据加入调料的不同进行加收费用。
2.2.1 第一版设计方案
员工A收到这个需求后,觉得很简单,于是他仅用了半个小时就给出了自己的方案:将现有的咖啡类和所有调料排列组合新建所有可能的饮料类,UML如下:
依次类推,后面新增的话还是根据这个思路增加类。
员工B看到后,不经感叹到: 这简直是无脑堆砌, 你最好不要给经理看到你这设计,不然你极有可能需要去逛招聘网站了。很明显,你在为自己创造一个维护恶梦,加入牛奶的价钱上涨或下降,想想你该怎么应对!!
于是员工A接纳了员工B的意见,仔细思考后,他给出了第二版方案
2.2.2 第二版设计方案
员工A发现,如果将调料维护在基类Beverage,也许会好很多:
将调料作为饮料的boolean属性,计算价格时,根据设置的值来进行具体的计算,这个方案似乎可行,下面我们来实现看看:
package DecoratorPattern.second.base;
public abstract class Beverage {
private String description;
private boolean milk;//定价1块
private boolean soy;//定价2块
private boolean mocha;//定价3块
private boolean whip;//定价4块
public Beverage(boolean milk,boolean soy,boolean mocha,boolean whip){
this.milk = milk;
this.soy = soy;
this.mocha = mocha;
this.whip = whip;
}
public double cost(){
double addMoney = 0;
if(hasMilk()) addMoney+=1;
if(hasMocha()) addMoney+=3;
if(hasSoy()) addMoney+=2;
if(hasWhip()) addMoney+=4;
return addMoney;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
public void setSoy(boolean soy) {
this.soy = soy;
}
public void setMocha(boolean mocha) {
this.mocha = mocha;
}
public void setWhip(boolean whip) {
this.whip = whip;
}
public boolean hasMilk() {
return milk;
}
public boolean hasSoy() {
return soy;
}
public boolean hasMocha() {
return mocha;
}
public boolean hasWhip() {
return whip;
}
}
然后编写具体的饮料类:
package DecoratorPattern.second.coffee;
import DecoratorPattern.second.base.Beverage;
/**
* Decaf 定价 8元
*/
public class Decaf extends Beverage {
public Decaf(boolean milk,boolean soy,boolean mocha,boolean whip){
super(milk,soy,mocha,whip);
}
@Override
public double cost() {
return 8+super.cost();
}
}
package DecoratorPattern.second.coffee;
import DecoratorPattern.second.base.Beverage;
/**
* HouseBlend 定价 5元
*/
public class HouseBlend extends Beverage {
public HouseBlend(boolean milk, boolean soy, boolean mocha, boolean whip) {
super(milk, soy, mocha, whip);
}
@Override
public double cost() {
return 5+super.cost();
}
}
package DecoratorPattern.second.coffee;
import DecoratorPattern.second.base.Beverage;
/**
* DarkRoast 定价 10元
*/
public class DarkRoast extends Beverage {
public DarkRoast(boolean milk, boolean soy, boolean mocha, boolean whip) {
super(milk, soy, mocha, whip);
}
@Override
public double cost() {
return 10+super.cost();
}
}
用上面这种设计我们进行使用时确实可以避免繁多的类编写:
package DecoratorPattern.second;
import DecoratorPattern.second.coffee.Decaf;
public class MainTest {
public static void main(String[] args) {
//普通的Decaf
System.out.println(new Decaf(false,false,false,false).cost());
//加牛奶的Decaf
System.out.println(new Decaf(true,false,false,false).cost());
}
}
很明显,我们可以在使用时传入boolean参数来为饮料主体添加调料即可,在计算时会自己根据参数计算出正确的价钱,这次应该既满足了需求,也比较容易维护了吧。
2.3 第二次需求
在实际的服务过程中,有的用户比较喜欢某一个调料,比如他想加双倍的牛奶,这时,我们的系统好像满足不了顾客了,于是业务部门想要你使其满足支持n倍的调料的饮料价格计算。
员工A思考了一会儿,说,这个可以实现,我们只需要修改Beverage中的调料从boolean类型为int类型的不就行了。
员工B: 那样做是不是就意味着,所有继承它的子类都要进行修改? 如果它的子类很多的话,岂不是噩梦!!
的确,员工A没有考虑到这一点,这时他仿佛记起了一个原则: 类应该对扩展开放,对修改关闭。
于是,他带着思考,接着寻求更佳的设计。
既然调料会出现无法预计的使用方式,那么干脆就将每个调料作为一个个体类进行设计,这样也许会好一点,员工A尝试着设计,我们可以以饮料为主体,然后在运行时用调料来给饮料附加价钱计算,就像代理模式那样,动态的为饮料的cost方法增强。
首先,基类代码如下:
package DecoratorPattern.third.base;
/**
* 饮料抽象类
*/
public abstract class Beverage {
public String description = "Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
其次,调料基类如下:
package DecoratorPattern.third.base;
/**
* 调料抽象类
*/
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
然后就是饮料类:(这里只展示一个)
package DecoratorPattern.third.coffee;
import DecoratorPattern.third.base.*;
/**
*首选咖啡实体类
*/
public class HouseBlend extends Beverage {
@Override
public double cost() {
return 10;
}
public HouseBlend(){
description = "House Blend Coffee";
}
}
然后就是调料类:
package DecoratorPattern.third.smallCase;
import DecoratorPattern.third.base.Beverage;
import DecoratorPattern.third.base.CondimentDecorator;
/**
* Mocha调料
*/
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public double cost() {
return 2+ beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription()+ ", Mocha";
}
}
下面我们进行测试:
package DecoratorPattern.third;
import DecoratorPattern.third.coffee.HouseBlend;
import DecoratorPattern.third.smallCase.Mocha;
import DecoratorPattern.third.base.Beverage;
/**
* 装饰者模式:动态的将责任附到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
*/
public class StarBuzzCoffee {
public static void main(String[] args) {
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
System.out.println(beverage2.cost());
}
}
成功给HouseBlend饮料加了双倍的Mocha调料并计算出了价格。
这,就是装饰者模式:
装饰者模式动态的将责任附加到对象上。 如果要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者模式是继承和组合的中和作用发生奇妙反应的产物。
在JDK中的I/O流中用到了装饰者模式,感兴趣的同学可以自己去看一下源码,看看是否和这个模式匹配。