Head First Design Mode(4)-装饰者模式
该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
装饰者模式:
装饰对象;本章可以称为“给爱用继承的人一个全新的设计眼界”;
讨论典型的继承滥用问题;
使用对象组合的方式,做到在运行时装饰类;
一旦熟悉了装饰技巧,能在不修改底层代码的情况下,给对象赋予新的职责;
星巴克咖啡准本更新订单系统,以满足饮料的供应
类设计:
Beverage(饮料)是一个抽象类,店内饮品均需继承此类;
cost()是抽象方法,子类定义具体的实现,返回饮料的价钱;
description实例变量 用来描述饮料;
场景:
购买咖啡的时候,需要加入各种配料,如蒸奶、豆浆、摩卡… 我们简单表示为Burden1 Burden2…
星巴克会根据锁加入的调料收取不同的费用;
一种尝试:
创建新类表示加入配料后的饮料,继承自Beverage;
如,Coffee1_Burden1 该类重写cost方法,计算出加上配料后的价钱;
可预见的问题:
会导致很多类,真的很多;
如果想修改某一调料的价格,或是新增一种调料,和已有的各个类搭配出新类,那简直就是一场灾难;
同时这样也违反了我们之前的设计原则,而且很严重;
另一种尝试:
将各种调料的有无,在超类中通过实例变量来标识;
如果指定的配料存在,那么实例变量如hasMilk返回TRUE,依照此状态,在Beverage中的cost方法里,计算相应的价格;
这些表示是否有配料的状态都有各自的set方法进行值得设置;
仍然存在的问题:
调整价钱,必须修改现有的代码;
一旦出现了新的配料,不得不加上新的方法,且需改变超类的cost()方法;
如果加入新的Coffee类(新饮品),但是这个饮品并不适合加milk,那么这个类就继承了不可使的hasMilk()和setMilk()方法;
如果想要Double milk,该怎么办…
大师指路:
组合(composition)和委托(delegation)可以在运行时具有继承行为的效果;
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为;
然而,如果能用组合的做法扩展对象的习惯那位,就可以在运行时动态的进行扩展;
通过动态的组合,可以写新的代码,加入新的功能,而无需改变现有代码;可维护性提高,产生副作用的机会也将减少;
开放-关闭原则:
类应该对扩展开放,对修改关闭;
这,最重要的设计原则之一;
我们的目标是允许类容易扩展,在不修改代码的情况下,加入新的行为;
这样的设计具有弹性应对改变,可以接受新的功能来应对改变的需求;
这听起来,可能感觉矛盾,但一些聪明的OO技巧,允许系统在不修改代码的情况下,进行功能扩展:
比如观察者模式,通过新加入的观察者,可以在任何时候扩展主题,而不需要在主题中加入代码;
还有很多经验的实证,可通过提供扩展的方法来保护代码免于被修改,如即将介绍的装饰者模式;
关于开-闭原则的使用:
遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度,而且难理解;
所以需要把注意力集中在设计中最有可能改变的地方,然后再使用该原则;
认识装饰者模式:
星巴克的问题:类数量爆炸、设计死板、以及新类加入的功能并不适用于所有的子类;
不一样的做法:以饮料为主体,在运行时以调料来“装饰”(decorate)饮料;
加入Burden1和Burden2的Coffee1:
取一个Coffee1对象;以Burden1装饰它;再以Burden2装饰它;调用cost()方法,并依赖委托将调料价钱加上去;
把装饰者对象当成“包装者”:
Burden虽然是装饰者,但是他的类型与它所装饰的对象类型需一致;
即Burden也有一个cost()方法,通过多态,经Burden包装过的Beverage(Coffee)依然是Beverage;
在cost()方法中,调用最外层的装饰者的cost方法,会先委托他装饰的对象调用其cost()方法,再加上当前装饰者需要加上的价格;
已经认识到的装饰者模式:
装饰者和被装饰者对象有相同的超类型,在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替;
可以用一个或多个装饰者包装一个对象;
装饰者可以在所委托被装饰者的行为之前或,之后,加上自己的行为,以达到特定的目的;
对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的使用需要和装饰者来装饰对象;
定义装饰者模式:
动态地将责任附加到对象上;
若要扩展功能,装饰者提供了比继承更有弹性的替代方案;
装饰者模式类图:
ConcreteComponent扩展字Component;
ConcreteDecorator有一个实例变量,可以记录所装饰的事物;
Decorator在这里是抽象类,也可以是装饰者共同实现的接口;
装饰者可以加上新的方法,新行为是通过在旧行为前面或后面做一些计算来添加的;
装饰者模式中,我们使用了继承达到“类型匹配”的目的,而不是利用继承获得“行为”;
我们的装饰者和组件组合时,就可以加上新的行为,这些行为是由对象组合来的,而不是继承自超类;
换句话说,行为来自装饰者和基础组件,或与其他装饰者之间的组合关系;
装饰我们的饮料:
Beverage抽象类:
public abstract class Beverage{
String description = "Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
Decorator抽象类:
public abstract class Decorator extends Beverage{
public abstract String getDescription();
}
Beverage具体类:Coffee1 Coffee2
public class Coffee1 extends Beverage{
public Coffee1(){
description = "Coffee1";
}
public double cost(){
return 5;
}
}
public class Coffee2 extends Beverage{
public Coffee2(){
description = "Coffee2";
}
public double cost(){
return 5.5;
}
}
Decorator具体类:Burden1 Burden2
public class Burden1 extends Decorator{
Beverage beverage;
public Burden1(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + "_Burden1";
}
public double cost(){
return 4 + beverage.cost();
}
}
public class Burden2 extends Decorator{
Beverage beverage;
public Burden2(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + "_Burden2";
}
public double cost(){
return 3 + beverage.cost();
}
}
DriverDecorator程序入口类:
public class DriveDecorator{
public static void main(String[] args){
Beverage beverage1 = new Coffee1();
Beverage beverage2 = new Coffee2();
System.out.println(beverage1.getDescription() + "$" + beverage1.cost());
System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
beverage2 = new Burden1(beverage2);
System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
beverage2 = new Burden2(beverage2);
System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
}
}
编译运行程序:
bogon:第三章装饰者模式 huaqiang$ javac *.java
bogon:第三章装饰者模式 huaqiang$ java DriveDecorator
Coffee1$5.0
Coffee2$5.5
Coffee2_Burden1$9.5
Coffee2_Burden1_Burden2$12.5
装饰者该做的事,就是增加行为到被包装对象上;
思考:在菜单上增加咖啡的容量大小,小杯(tall)中杯(grande)大杯(venti);
真实世界的装饰者:Java I/O
java.io包内的类很多,很多;
如果你知道了装饰者模式,就很好理解了,因为其中很多了都是装饰者;
Java I/O类图:
LineNumberInputStream是一个具体的装饰者,它加上了计算行数的能力;
BufferedInputStream是一个具体的装饰者,可以利用缓冲输入来改进性能,用readLine()方法来增强接口;
FileInputStream是被装饰的“组件”,Java I/O组件类都提供了最基本的字节读取功能;
FilterInputStream相当于一个抽象的装饰类(实际在文档中这是一个具体的类,并不是一个抽象类);
java中输入流/输出流的设计方式也类似(Reader/Writer流);
装饰者模式会引入一个缺点:
利用装饰者模式,尝尝造成设计中有大量的小类,数量会很多;
因此,装饰者通常是用类似工厂(Factory)或生成器(Builder)这样的模式创建的,他们会封装的很好,方便使用;
编写自己的Java I/O装饰者:
编写一个装饰者,把输入流内的所有大写字符转成小写;
test.txt文本:Hello World!
LowerCaseInputStream:
import java.io.*;
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(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;
}
}
DriverInoutText:
import java.io.*;
public class DriverInoutText{
public static void main(String[] args) throws IOException{
int c = 0;
try{
InputStream in = new BufferedInputStream(new FileInputStream("test.txt"));
in = new LowerCaseInputStream(in);
while((c = in.read()) >= 0){
System.out.print((char)c);
}
in.close();
}catch(IOException e){
System.out.println((char)c);
}
System.out.println("");
}
}
编译运行程序:
hello world!bogon:JavaIO huaqiang$ javac *.java
bogon:JavaIO huaqiang$ java DriverInoutText
hello world!
总结:
1.继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式;
2.在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码;
3.组合和委托可用于在运行时动态的加上新的行为;
4.除了继承,装饰者模式也可以让我们扩展行为;
5.装饰者模式意味着一群装饰者类,这些类用来包装具体组件;
6.装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现);
7.装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的;
8.可以用无数个装饰者包装一个组件;
9.装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型;
10.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂;
OO基础:
抽象;
封装
继承;
多态;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为交互对象之间的松耦合设计而努力;
——对扩展开放,对修改关闭;
OO模式:
策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;
观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;
——装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;