需求分析
有一家咖啡店,生意火爆,目前正在大量新增新型咖啡。
目前有
- HouseBlend 首选咖啡 、
- DarkRoast 烘焙咖啡、
- Decaf 低咖、
- Espresso 浓缩咖啡、
第一次设计
定义饮料超类
public abstract class Beverage {String description;public String getDescription() {return description;}abstract void cost();}
定义首选咖啡
public class HouseBlend extends Beverage {@Overridepublic String getDescription() {description = "我是 HouseBlend ";return description;}void cost() {System.out.println("我价值 1 元");}}
定义烘焙咖啡
public class DarkRoast extends Beverage {@Overridepublic String getDescription() {description = "我是 darkRoast";return description;}void cost() {System.out.println("我价值 2 元");}}
测试
public class Test {public static void main(String[] args) {Beverage houseBlend = new HouseBlend();System.out.println(houseBlend.getDescription());houseBlend.cost();Beverage darkRoast = new DarkRoast();System.out.println(darkRoast.getDescription());darkRoast.cost();}}**结果我是 HouseBlend我价值 1 元我是 darkRoast我价值 2 元
新需求来了
资本家现在想给原有的咖啡中加上调料, 例如蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha)···
加上调料就要有不同的产品描述和新的价格。
于是,他们这样做····
首选咖啡搭配蒸奶和摩卡 第一个解决方案
public class HouseBlendWithSteamedMilkAndMocha extends Beverage {@Overridepublic String getDescription() {description = "我是 HouseBlendWithSteamedMilkAndMocha";return description;}void cost() {System.out.println("我的价值是 hoseBlend + SteamedMill + Mocha 实为 1 + 1 + 1 = 3元");}}
·····
我有无数中搭配方案,那么就有无数个新类。
note: 这样肯定是不可取的,首先增加维护成本,其次如果要修改一种调料的价格,会发现 牵一发而动全身了。
第二个解决方案
将所有的调料放在超类中,根据boolean类型来判断子类是否需要调料
新的饮料抽象超类
public abstract class Beverage {public String description;boolean milk;boolean soy;boolean mocha;public String getDescription() {return description;}void cost(){if(isMilk()){System.out.println("加milk,2元");}if(isMocha()){System.out.println("加mocha,3元");}if(isSoy()){System.out.println("加soy,5元");}};public boolean isMilk() {return milk;}public void setMilk(boolean milk) {this.milk = milk;}public boolean isSoy() {return soy;}public void setSoy(boolean soy) {this.soy = soy;}public boolean isMocha() {return mocha;}public void setMocha(boolean mocha) {this.mocha = mocha;}}
新的烘焙咖啡DarkRoast
public class DarkRoast extends Beverage {@Overridepublic String getDescription() {description = "我是 darkRoast";return description;}void cost() {System.out.println("我价值 2 元");setMilk(true);super.cost();}}
新的首选咖啡HouseBlend
public class HouseBlend extends Beverage {@Overridepublic String getDescription() {description = "我是 HouseBlend ";return description;}void cost() {System.out.println("我价值 1 元");setMocha(true);setSoy(true);super.cost();}}
测试
Beverage darkRoast =new DarkRoast();System.out.println(darkRoast.getDescription());darkRoast.cost();Beverage houseBlend = new HouseBlend();System.out.println(houseBlend.getDescription());houseBlend.cost();我是 darkRoast我价值 2 元加milk,2元我是 HouseBlend我价值 1 元加mocha,3元加soy,5元
note: 可以发现,超类将所有调料的管理起来了,利用继承解决了类爆炸问题,弥补了第一种解决方案的不足。
不足之处
- 一旦出现新的调料,那么超类中的cost方案就要修改一次。
- 有些饮料不需要这些某些调料,但是用继承的话就会继承到那些无用的变量
第二次设计,巧妙利用组合与委托代替继承
note : 因为要返回饮料的价格,所以适当调整了cost方法, 并且定义了描述的默认值:未知饮料
超类 Beverage
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() {return description;}public abstract double cost();}
调味品装饰类 CondimentDecorator
首先,装饰类是一个抽象类
其次,必须能让装饰类能够替代被装饰类。 这边用继承达到这个效果。
再者,它要重写getDescription方法
public abstract class CondimentDecorator extends Beverage {public abstract String getDescription();}
首选咖啡HouseBlend 继承自Beverage
public class HouseBlend extends Beverage {public HouseBlend(){description = "House Blend Coffee";}public double cost() {return 0.89;}}
烘焙咖啡 DarkRoast 继承自Beverage
public class DarkRoast extends Beverage {public DarkRoast(){description = "Dark Roast Coffee";}public double cost() {return 1.9;}}
调料 Mocha 继承自 CondimentDecorator
public class Mocha extends CondimentDecorator {Beverage beverage;public Mocha(Beverage beverage){this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return 1.2 + beverage.cost();}}
调料Soy 继承自 CondimentDecorator
public class Soy extends CondimentDecorator {Beverage beverage;public Soy(Beverage beverage){this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Soy";}public double cost() {return 3.3 + beverage.cost();}}
测试:
定一杯纯HouseBlend
public static void main(String[] args) {Beverage beverage = new HouseBlend();System.out.println(beverage.getDescription() + " ¥"+beverage.cost());}//结果House Blend Coffee ¥0.89
定一杯HouseBlend + Soy
public static void main(String[] args) {Beverage beverage = new HouseBlend();beverage = new Soy(beverage); //用soy装饰HouseBlendSystem.out.println(beverage.getDescription() + " ¥"+beverage.cost());}//结果:House Blend Coffee, Soy ¥4.1899999999999995
定一杯DarkRoast + soy + mocha
public static void main(String[] args) {Beverage beverage = new DarkRoast();beverage = new Soy(beverage);beverage = new Mocha(beverage); //两个一起装饰 darkroastSystem.out.println(beverage.getDescription() + " ¥"+beverage.cost());}//结果Dark Roast Coffee, Soy, Mocha ¥6.3999999999999995
小结
在最后一次设计中,所有的调料变成了装饰者。
当一杯饮料需要用到调料时,只需要用该调料装饰着给饮料包裹一层就好了。
比如:
new Soy(某饮料)
note : 会发现这一套下来有些像链式编程。 外层装饰者调用内层饮料。
下定义
装饰者模式 动态地将责任附加到对象上! 若要扩展功能,装饰者提供了比继承更有弹性的替代方案(组合+委托) 装饰者模式完全遵循了 开放-闭合的设计原则, 即对扩展开发,对修改关闭。
JDK实现
JAVA/IO
BufferInputStream 就是FileInputStream的装饰者
探索
前面说到,装饰类必须能够替代被装饰者类。
那么猜测, BufferInputStream这个具体装饰者实现类,它的超类一定继承于FileInputStream的超类,也就是说一定继承于InputStream抽象类。
