装饰者模式
概念:动态的将责任附加到对象上。若要扩展该功能。装饰者提供了比继承更有弹性的替代方案。
要点:
继承属于扩展的形式之一,但不见得是达到弹性设计的最佳方案。
组合和委托可用于在运动的时候动态的加上新的行为。
装饰者一把对组件的客户是透明的,除非客户程序依赖于组件的具体实现。
装饰者会导致设计中出现许多小对象。如果使用过度,会让程序变得很复杂。
例子:
星巴兹咖啡准备更新订单系统,以合乎他们的饮料供应需求。
他们原先的类设计为:
这样的订单系统没有办法考虑到咖啡调料的部分,把加入不同调料的咖啡看做不同的类会导致类爆炸(每个类的cost方法计算出咖啡加调料的价钱):
很明显,这样的系统难以维护,一旦牛奶的价钱上扬或新增一种焦糖调料,系统将难以改变。
采用实例变量和继承的设计也许能解决一些问题:
Beverage作为一个饮料类,加上实例变量代表是否加入了饮料。
然而当用户想要双倍摩卡咖啡时,这样的系统就显得有些无所适从。
对于冰茶,饮料基类里的有些调料根本不适用,但是也一起继承了过来!
到目前为止,使用继承会造成的问题有:类爆炸,设计死板,以及基类加入的新功能并不适用于所有的子类。
所以继承并不是解决问题的方法,应当使用组合来使系统更有弹性且易于维护。
开放-关闭原则:
设计原则:
类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
这个目标需要使用装饰着模式实现:以饮料为主体,然后运行调料来“装饰”饮料。
如图为一个摩卡和奶泡DarkRoast咖啡的设计图:
定义装饰者模式:
装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更具有弹性的替代方案。
装饰者模式类图:
现在让星巴兹咖啡系统也符合装饰者类图:
具体实现:
从饮料下手,将饮料作为一个抽象类:
- package com.cafe;
- public abstract class Beverage {
- String description = "Unknow Beverage";
- public String getDescription() {
- return description;
- }
- public abstract double cost();
- }
调料抽象类,也就是装饰者类:
- package com.cafe;
- public abstract class CondimentDecorator extends Beverage{
- public abstract String getDescription();
- }
实现具体的饮料(浓缩咖啡和综合咖啡):
- package com.cafe;
- public class Espresso extends Beverage {
- public Espresso() {
- description = "Espresso";
- }
- public double cost() {
- return 1.99;
- }
- }
- package com.cafe;
- public class HouseBlend extends Beverage {
- public HouseBlend() {
- description = "HouseBlend";
- }
- public double cost() {
- return 0.89;
- }
- }
实现具体装饰者类(摩卡)
- package com.cafe;
- 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 0.20+beverage.cost();
- }
- }
其他装饰者类的实现方式与摩卡类似。
测试代码:
- package com.cafe;
- public class StartbuzzCoffee {
- public static void main(String args[]) {
- Beverage beverage1 = new Espresso();
- System.out.println(beverage1.getDescription() + " $"
- + beverage1.cost());
- Beverage beverage2 = new HouseBlend();
- beverage2 = new Soy(beverage2);
- beverage2 = new Mocha(beverage2);
- beverage2 = new Whip(beverage2);
- System.out.println(beverage2.getDescription() + " $"
- + beverage2.cost());
- }
- }
测试结果:
JAVA中的装饰者模式(java.io类):
Java I/O引出装饰者模式的一个“缺点”:利用装饰者模式,会造成设计中存在大量的小类。
编写自己的Java I/O装饰者,把输入流中的所有大写字母转成小写:
- package com.io;
- import java.io.*;
- public class InputTest {
- public static void main(String[] args) throws IOException {
- int c;
- try {
- InputStream in = new LowerCaseInputStream(new BufferedInputStream(
- new FileInputStream("D:\\test.txt")));
- while ((c = in.read()) >= 0) {
- System.out.print((char) c);
- }
- in.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
测试程序(测试刚刚写好的I/O装饰者)
- package com.io;
- import java.io.FilterInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- public class LowerCaseInputStream extends FilterInputStream {
- protected LowerCaseInputStream(InputStream in) {
- super(in);
- // TODO Auto-generated constructor stub
- }
- 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;
- }
- }
测试结果: