3 Decorator Pattern(装饰者模式)
3.1设计原则一类应该对扩展开放,对修改关闭
前言:装饰者模式主要是为了解决继承滥用的问题,以下将使用对象组合的方式做到在运行时装饰类。
1)案例分析一:
REQ1:星星吧咖啡店咖啡种类扩展飞快,Vander作为其老板,准备尽快更新订单系统来满足这一发展。原先的设计如下:
分析:随着饮品的发展,每种饮料都可以自由搭配,而且本身饮料也很多,除了咖啡之外,鸳鸯、奶茶、可乐、雪碧、酸奶、豆浆等等,而且调料还可以自由选择。使用不同的调料需要付不同的价格,例如一小份牛奶加入咖啡中,加收1块钱等等。如果继续按照上述的设计继续,则继承Beverage抽象类的饮料将非常多,例如coffeewithonemilk,milkTeaWithSteamAndSoy等等,这样能产生无数的搭配,并且牛奶价格上升之后,每个涉及到牛奶的类的cost函数还需要修改,这简直就是噩梦。
解决方法1:Vander 就开始设计了
超类cost()将计算所有调料的价格,子类覆盖的cost()方法扩展超类的功能,把指定的饮料类型的价钱也加上。
public class Beverage {
private String desc;
private boolean milk;
private boolean soy;
private boolean mocha;
private boolean whip;
public boolean hasMilk() {
return this.milk;
}
public boolean hasSoy() {
return this.soy;
}
public boolean hasMocha() {
return this.mocha;
}
public boolean hasWhip() {
return this.whip;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
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 float cost() {
float flavourCost = 0.0f;
if(hasMilk()) {
flavourCost = flavourCost + 1.0f;
}
if(hasSoy()) {
flavourCost = flavourCost + 2.0f;
}
if(hasMocha()) {
flavourCost = flavourCost + 3.0f;
}
if(hasWhip()) {
flavourCost = flavourCost + 4.0f;
}
return flavourCost;
}
}
public class DarkRoast extends Beverage {
private float cost;
public float getCost() {
return cost + super.cost();
}
public void setCost(float cost) {
this.cost = cost;
}
}
存在的问题:
这个方法出现了以下4个问题:
1、当调料价格改变的时候需要修改Beverage类的代码。
2、一旦有新的调料,需要加上新的方法,并改变超类中的cost()方法。
3、如果有新的饮料(Tea),对这些饮料而言,某些调料(如soy、stream等)可能并不适合,但是这个设计方式中,Tea子类仍将继承那些不合适的方法,例如:hasSoy()(加入豆浆)
4、顾客万一不只是要一份摩卡,想加入两份摩卡调料,上述的方法根本无法应对。
解决方法3(使用装饰者模式):
REQ2:首先有这么一种需求,顾客买了一杯DarkRoast,想加Mocha,然后再加奶泡Whip,最后要计算这杯DarkRoast的金额。
装饰者模式可以用下面的图来说明,该图上可以看到Whip包裹着Mocha,而Mocha又包裹了DarkRoast,并且这三个类的基类都是Beverage,DarkRoast继承自Beverage,且有一个用来计算费用的cost()方法,Mocha对象是一个装饰者,它的类型反映了它所装饰的对象;Whip也是一个装饰者,所以它也反映了DarkRoast类型,并包括一个cost()方法。
最后到结账的时候,先调用最外层的Whip,得到了Whip的价格,然后再调用Mocha的cost,此时Whip的价格传给了Mocha,这样Mocha再加上自己的价格,现在就得到了Mocha+Whip的价格,然后再调用DarkRoast的价格,最后就得到了这杯咖啡的价格。这样相当于就做到“在运行时决定类的行为”。
这里要说明的是,Beverage可以用接口也可以用抽象类,若需要加入属性的话,就使用抽象类,若不需要属性则可以使用接口,方便日后代码可以extends其他的类。
以下是关键代码:
Beverage beverage = new DarkRoast();
beverage = new Whip(beverage);
beverage = new Mocha(beverage);
public class Mocha implements CondimentDecorator {
private Beverage beverage;
public float cost() {
return this.beverage.cost() + 1.0f;
}
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDesc() {
return this.beverage.getDesc() + " with " + "Mocha";
}
}
REQ3:那么问题来了,现实的Java世界中有哪些用了装饰者模式呢,你是否看过这样的一句new LineNumberInputStream(new BufferedInputStream(new FileInputStream)),这句话看似很复杂,其实这就是典型的装饰者模式,FileInputStream是被装饰的“组件”,Java I/O程序库提供了几个组件,包括了FileInputStream、StringBufferInputStream、ByteArrayInputStream… …等。这些类都提供了最基本的字节读取功能。
BfferedInputStream是具体的装饰者,加入了两种行为,利用缓存输入来改进性能,用readline()方法(用来一次读取一行文本输入数据)来增强接口。
LineNumberInputStream也是一个具体的装饰者,它加上了计算行数的功能。
下面进行一个小练习,写一个IO装饰者来讲输入流中所有的大写字母转成小写。
public class LowcaseInputStream extends FilterInputStream {
public LowcaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return ( c == -1? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for(int i = offset; i < offset + result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
public class Main {
public static void main(String[] args) {
InputStream inputStream;
int c;
try {
inputStream = new FileInputStream("test.txt");
inputStream = new BufferedInputStream(inputStream);
inputStream = new LowcaseInputStream(inputStream);
while((c = inputStream.read()) >= 0) {
System.out.print((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
装饰者模式的缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能造成使用此API程序员的困扰。但是其实了解了装饰者的原理,就可以容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的功能。
面向对象基础
抽象、封装、多态、继承
四大原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程。
设计原则三:多用组合,少用继承。
设计原则四:为交互对象之间的松耦合设计而努力。
设计原则五:对扩展开放,对修改关闭。
模式
装饰者模式:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。