4. 装饰模式(Decorator Pattern)
装饰模式顾名思义,就是给对象进行一些装饰,让他在原有的基础上动态的添加额外的职责。就好象一个女人,你可以给她"装饰"上漂亮的衣服,做好护肤,买漂亮的首饰然后让他成为一个美女;也可以不给她一顿饱饭,让她天天干活成为黄脸婆,这就是装饰的作用,在原来的基础上附加职责。而不用单独的去定义美女和黄脸婆两个类(世界上的女人那么多种,怎么定义的过来)。通过动态增加职责,可以有效的减少类的数量,还可以使程序的结构更"优雅"。
在Java类库中,IO类就大量的使用了装饰模式,比如说这个语句:
这句话就是用了BufferedInputStream去装饰FileInputStream增加了缓冲的功能。
还是延续以前的做法举个现实生活中的例子吧。
比如一个定制生日蛋糕的西饼屋,它的收费方式是这样的:
首先是蛋糕,分为6.8寸,10寸,12寸的等等,分大小来收费,然后加入的奶油和巧克力等等单独收费,为了简单起见,蛋糕的大小的要素就不考虑了,按理说大的蛋糕应该花的奶油的钱要多一些。
下面我们来编码实现这个西饼屋的计费程序。
最初,蛋糕和奶油和巧克力等配料不多,我们的实现是这样的。
首先是蛋糕的基类,它提供了一个抽象方法#price(),这个方法用于返回蛋糕的价格。
那么一个10寸的巧克力蛋糕就会如下所示的去定义
其他种类的蛋糕也可以用相似的方法,但是,很快,程序就遇到了问题,随着西饼屋的蛋糕配料越来越多,我们不得不去创建诸如ChocolateScream10InchCake(10寸巧克力奶油蛋糕),StrawberryScream10InchCake(10寸草莓奶油蛋糕)等等各种各样的子类,另外如果客户希望加入双份的草莓那么要怎么实现呢?这样下去子类的数量会是一个庞大的值,很明显,这种方式绝对不是一个好的设计。
下面,我们就要用装饰模式去改进它了。首先,还是要定义一个蛋糕基类的,这个和之前没什么区别
然后,定义一个10寸的蛋糕类
这个蛋糕单独卖是值20块钱的。
然后,可以定义一系列的配料类,首先定义一个配料基类
然后各种配料就可以继承自这个类来实现对蛋糕的装饰了。下面是奶油和巧克力的实现:
同样,也可以用类似的方式实现其他的配料。最后,一个订购巧克力奶油蛋糕和巧克力双份奶油的蛋糕的客户端实现如下:
它的运行结果如下:
上面描述的就是装饰模式了,它通过动态追加职责的方式有效的解决了"子类爆炸",另外出现需要撤销职责的情况,也可以使用装饰模式,因为这个方法并没有改变原来的对象。
最后给出装饰模式的类图:
装饰模式顾名思义,就是给对象进行一些装饰,让他在原有的基础上动态的添加额外的职责。就好象一个女人,你可以给她"装饰"上漂亮的衣服,做好护肤,买漂亮的首饰然后让他成为一个美女;也可以不给她一顿饱饭,让她天天干活成为黄脸婆,这就是装饰的作用,在原来的基础上附加职责。而不用单独的去定义美女和黄脸婆两个类(世界上的女人那么多种,怎么定义的过来)。通过动态增加职责,可以有效的减少类的数量,还可以使程序的结构更"优雅"。
在Java类库中,IO类就大量的使用了装饰模式,比如说这个语句:
- InputStream is = new BufferedInputStream(new FileInputStream("file"));
InputStream is = new BufferedInputStream(new FileInputStream("file"));
这句话就是用了BufferedInputStream去装饰FileInputStream增加了缓冲的功能。
还是延续以前的做法举个现实生活中的例子吧。
比如一个定制生日蛋糕的西饼屋,它的收费方式是这样的:
首先是蛋糕,分为6.8寸,10寸,12寸的等等,分大小来收费,然后加入的奶油和巧克力等等单独收费,为了简单起见,蛋糕的大小的要素就不考虑了,按理说大的蛋糕应该花的奶油的钱要多一些。
下面我们来编码实现这个西饼屋的计费程序。
最初,蛋糕和奶油和巧克力等配料不多,我们的实现是这样的。
首先是蛋糕的基类,它提供了一个抽象方法#price(),这个方法用于返回蛋糕的价格。
- // 蛋糕基类
- public abstract class Cake {
- protected String description;
- public String getDescription() {
- return this.description;
- }
- public abstract float price();
- }
// 蛋糕基类
public abstract class Cake {
protected String description;
public String getDescription() {
return this.description;
}
public abstract float price();
}
那么一个10寸的巧克力蛋糕就会如下所示的去定义
- public class Chocolate10InchCake extends Cake {
- @Override
- public float price() {
- return 50;
- }
- }
public class Chocolate10InchCake extends Cake {
@Override
public float price() {
return 50;
}
}
其他种类的蛋糕也可以用相似的方法,但是,很快,程序就遇到了问题,随着西饼屋的蛋糕配料越来越多,我们不得不去创建诸如ChocolateScream10InchCake(10寸巧克力奶油蛋糕),StrawberryScream10InchCake(10寸草莓奶油蛋糕)等等各种各样的子类,另外如果客户希望加入双份的草莓那么要怎么实现呢?这样下去子类的数量会是一个庞大的值,很明显,这种方式绝对不是一个好的设计。
下面,我们就要用装饰模式去改进它了。首先,还是要定义一个蛋糕基类的,这个和之前没什么区别
- // 蛋糕基类
- public abstract class Cake {
- protected String description;
- public String getDescription() {
- return this.description;
- }
- public abstract float price();
- }
// 蛋糕基类
public abstract class Cake {
protected String description;
public String getDescription() {
return this.description;
}
public abstract float price();
}
然后,定义一个10寸的蛋糕类
- public class TenInchCake extends Cake {
- public TenInchCake() {
- this.description = "10 inch cake";
- }
- @Override
- public float price() {
- return 20;
- }
- }
public class TenInchCake extends Cake {
public TenInchCake() {
this.description = "10 inch cake";
}
@Override
public float price() {
return 20;
}
}
这个蛋糕单独卖是值20块钱的。
然后,可以定义一系列的配料类,首先定义一个配料基类
- public abstract class CakeDecorator extends Cake {
- protected Cake cake;
- public CakeDecorator() {
- this.description = "cake decorator";
- }
- }
public abstract class CakeDecorator extends Cake {
protected Cake cake;
public CakeDecorator() {
this.description = "cake decorator";
}
}
然后各种配料就可以继承自这个类来实现对蛋糕的装饰了。下面是奶油和巧克力的实现:
- public class Scream extends CakeDecorator {
- public Scream(Cake cake) {
- this.cake = cake;
- this.description = this.cake.getDescription() + ", scream";
- }
- @Override
- public float price() {
- return 10 + this.cake.price();
- }
- }
- public class Chocolate extends CakeDecorator {
- public Chocolate(Cake cake) {
- this.cake = cake;
- this.description = this.cake.getDescription() + ", chocolate";
- }
- @Override
- public float price() {
- return 15 + this.cake.price();
- }
- }
public class Scream extends CakeDecorator {
public Scream(Cake cake) {
this.cake = cake;
this.description = this.cake.getDescription() + ", scream";
}
@Override
public float price() {
return 10 + this.cake.price();
}
}
public class Chocolate extends CakeDecorator {
public Chocolate(Cake cake) {
this.cake = cake;
this.description = this.cake.getDescription() + ", chocolate";
}
@Override
public float price() {
return 15 + this.cake.price();
}
}
同样,也可以用类似的方式实现其他的配料。最后,一个订购巧克力奶油蛋糕和巧克力双份奶油的蛋糕的客户端实现如下:
- public class Client {
- public static void main(String[] args) {
- Cake cake1 = new TenInchCake();
- cake1 = new Scream(cake1);
- cake1 = new Chocolate(cake1);
- System.out.println("Desc : " + cake1.getDescription());
- System.out.println("Price: " + cake1.price());
- System.out.println("----------------------------------");
- Cake cake2 = new TenInchCake();
- // 双份奶油
- cake2 = new Scream(cake2);
- cake2 = new Scream(cake2);
- cake2 = new Chocolate(cake2);
- System.out.println("Desc : " + cake2.getDescription());
- System.out.println("Price: " + cake2.price());
- }
- }
public class Client {
public static void main(String[] args) {
Cake cake1 = new TenInchCake();
cake1 = new Scream(cake1);
cake1 = new Chocolate(cake1);
System.out.println("Desc : " + cake1.getDescription());
System.out.println("Price: " + cake1.price());
System.out.println("----------------------------------");
Cake cake2 = new TenInchCake();
// 双份奶油
cake2 = new Scream(cake2);
cake2 = new Scream(cake2);
cake2 = new Chocolate(cake2);
System.out.println("Desc : " + cake2.getDescription());
System.out.println("Price: " + cake2.price());
}
}
它的运行结果如下:
- Desc : 10 inch cake, scream, chocolate
- Price: 45.0
- ----------------------------------
- Desc : 10 inch cake, scream, scream, chocolate
- Price: 55.0
Desc : 10 inch cake, scream, chocolate
Price: 45.0
----------------------------------
Desc : 10 inch cake, scream, scream, chocolate
Price: 55.0
上面描述的就是装饰模式了,它通过动态追加职责的方式有效的解决了"子类爆炸",另外出现需要撤销职责的情况,也可以使用装饰模式,因为这个方法并没有改变原来的对象。
最后给出装饰模式的类图: