这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。
我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。
我们这次要讲解的设计模式是装饰者模式
我们举个栗子:
我们需要为连锁咖啡店开发一个更新订单系统,以合乎它们的饮料供应要求
价钱分为两种:一种是不同品种咖啡的钱 另一种是不同添加的调料的钱
最初的设计是这样子的:
Beverage(饮料)是一个抽象类,店内所提供的所有饮料都必须继承这个类
Beverage类中 属性: description用来描述饮料 getDescription()获得描述信息 cost()获得价格信息 还有一些其他的方法
所有不同的饮料都继承自Beverage
第一个思路:
创造出添加不同调料配不同品种咖啡的类继承Beverage类,这样会使代码非常的冗杂,类简直是太多了
导致的问题:
1. 如果配料牛奶的价格增加了,要把添加了牛奶的咖啡的所有类都改变一下,这真的很复杂
2.当要增加一种口味的调料时,又要创造成几何增长的类
违反设计规则: 封装发生变化的代码,多用组合,少用继承
第二种思路:
在基类Beverage直接使用布尔实例变量来代表是否加上不同种类的调料,使用继承来继承不同set和get方法来设置是否添加调料
Beverage属性: description milk soy mocha whip 方法: getDescription() cost() hasMilk() setMilk() hasSoy() setSoy()等一系列getter()和setter()方法
使用不同品种咖啡来继承基类
出现的问题:
1.调料价钱的改变会使我们更改现有的代码
2.一旦出现新的调料,我们就需要加上新的方法(hasXxx()、setXxx()),并改变超类中的cost()方法(添加if判断是否加入了新调料,并调整返回的价格)
3.一旦开发除了新的饮品,比如茶,一些继承下来的调料并不适合,也继承了不适合的方法
4.如果客人想要添加双份的调料,这样调料价格就应该为2倍,这时候又该怎么办
得出的感悟:
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而, 如果能够利用组合
的做法扩展对象的行为,就可以在运行时动态地进行扩展。利用动态地组合对象,可以写新的代码添加新的功能,而无须修改现有代码,引进的bug或产生以外副作用的机会将大幅度减小。
设计原则:允许类容易拓展,在不修改现有代码的情况下,就可搭配新的行为。这样的设计具有弹性可以应对改变,
可以接受新的功能来应对改变的需求
即类应该对扩展开发,对修改关闭(如设计模式二之观察者模式,通过加入新的观察者,我们可以在任何时候拓展Subject(主题),而且不需向主题中添加代码) 应用这个原则的时候要将重点放在设计中最有可能改变的地方,胡乱用会增加代码的复杂性和阅读障碍。
下面是我们的装饰者模式出厂了:
装饰者模式: 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案
装饰者模式的构成:
1.Component(组件类)抽象类(或接口), 方法有methodA(),methodB()等方法
2.ConcreteComponent(组件具体实现类/被装饰者)是我们将要动态地加上新行为的对象,它继承自Component
3.Decorator(装饰者类) 继承自Component类,这是装饰者共同实现的抽象类(也可以是接口)
4.ConcreteDecoratorA等具体的装饰者类(也就是配料)
属性Component wrappedObj(用来记录所装饰的事物) 方法methodA() methodB() 其他方法
注意: Decorator抽象类是为了使ConcreteDecoratorA和被装饰者具有相同的类型,继承
达到"类型匹配"的目的,而不是利用继承获得"行为" ,"行为"是从被装饰者对象中获得的。
下面就是使用了装饰者模式的设计方案:
我们以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。
饮料就是组件抽象类,我们可以生成一系列不同品种的饮料组件;而装饰者就是不同的配料
直接看代码部分:
Beverage饮料组件抽象类以及调料抽象类(继承组件抽象类)
public abstract class Beverage{
String description = "Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription(); //所有调料装饰者必须重新实现getDescription()方法
}
各种组件饮料的具体类(Espresso类、HouseBlend类)
public class Espresso extends Beverage{
public Espresso(){
description = "Espresso";
}
public double cost(){
return 1.99;
}
}
public class HouseBlend extends Beverage{
public Espresso(){
description = "HouseBlend";
}
public double cost(){
return .89;
}
}
装饰者调料具体实现类Mocha Soy Whip
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
reuturn beverage.getDescription() + ", Mocha";
}
public double cost(){
return .20 + beverage.cost();
}
}
public class Soy extends CondimentDecorator{
Beverage beverage;
public Soy(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
reuturn beverage.getDescription() + ", Soy";
}
public double cost(){
return .15 + beverage.cost();
}
}
public class Whip extends CondimentDecorator{
Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
reuturn beverage.getDescription() + ", Whip";
}
public double cost(){
return .3 + beverage.cost();
}
}
测试代码
public class StartbuzzCoffe{
public static void main(String args[]){
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
//这里我们点了两份Mocha配料
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
}
}
这种模式的缺点:
1.如果代码针对特定种类的具体组件,做一些特殊的事,如打折,一旦用包装着包装它,就会造成类型改变。
2.采用装饰者在实例化组件时,会增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还要把此组件
包装进装饰者中,如果有多个调料,那么就要包装多少次。 我们以后会谈到Factory(工厂模式),和生成器模式(Builder)
,可以解决此问题
3.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
下面我们讲解java Api中关于装饰者模式的应用
java.io包装关于输入输出流的类大都用了装饰者模式
比如:
抽象组件: InputStream
具体组件: FileInputStream、StringBufferedInputStream、ByteArrayInputStream等为抽象组件子类
抽象装饰者: FilterInputStream为抽象组件子类
具体装饰者: PushbackInputStream、BufferedInputStream(加入了缓冲输入来改善性能、用一个readline()方法读取一行文本输入数据)、DataInputStream、LineNumberInputStream(增加计算行数的能力)为抽象装饰者子类
实现自己的Java I/O装饰者
将所有大写字符转换成小写,例如将"HELLO WORLD"转换成"hello world"
//FilterInputStream是所有InputStream组件的抽象装饰者
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 < result + offset; i++){
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
public class InputTest{
public static void main(String[] args) throws IoException{
int c;
try{
InputStream in = new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while((c = in.read()) >= 0){
System.out.print((char)c);
}
in.close();
} catch(IOException e){
e.printStackTraace();
}
}
}
下一次我们将介绍工厂模式啦!