装饰者模式

1 场景举例

现在有一个咖啡厅,里面有咖啡种类(如:意式咖啡、美式咖啡、浓咖啡、无因咖啡等),客户可以选择单点咖啡,或者往咖啡里加其他辅助调味品(如:牛奶、巧克力、豆浆等),每加一份调味品,咖啡的价格自然要增加,最后计算咖啡的总价。针对这个问题,我们应该如何给出合适的设计呢?

2 常规解决方案

最容易想到的一种解决方案是,直接将所有的咖啡种类和辅助调味品进行排列组合,然后在具体的子类中实现计算价格的方法。

排列组合咖啡种类和调味品

这种方法可以满足开闭原则,但是存在一个很大的问题:如果咖啡种类和调味品种类都增加时,子类的数量将会呈指数级的增加,产生类爆炸。这和我们前面谈到的桥接模式有点类似,但是不同点在于这里无法做到接口与抽象分离,因为咖啡种类能组合的调味品的数量是不确定的。

3 装饰者模式

装饰者模式是动态地将新功能附加到对象身上,在对象功能扩展方面比继承更具有弹性。下面我们就用装饰者模式来解决咖啡订单问题。
首先,有一个抽象基类Drink,在这个抽象类中,有desc成员变量代表咖啡种类或者调味品种类的描述,成员变量price代表单价,抽象方法cost表示整个咖啡的价格。

public abstract class Drink {
    protected String desc;

    private float price = 0.0f;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public abstract float cost();
}

咖啡类:继承Drink,实现了cost方法

public class Coffee extends Drink {
    @Override
    public float cost() {
        return super.getPrice();
    }
}

咖啡种类:意式咖啡、美式咖啡、浓咖啡、无因咖啡都继承自咖啡类

public class Espresso extends Coffee {
    public Espresso() {
        setDesc(" 意式咖啡 ");
        setPrice(6.0f);
    }
}
public class LongBlack extends Coffee {
    public LongBlack() {
        setDesc(" 美式咖啡 ");
        setPrice(5.0f);
    }
}
public class ShortBlack extends Coffee {
    public ShortBlack() {
        setDesc(" 浓咖啡 ");
        setPrice(4.0f);
    }
}
public class DeCof extends Coffee {
    public DeCof() {
        setDesc(" 无因咖啡 ");
        setPrice(1.0f);
    }
}

装饰类:继承自Drink,内部有一个Drink类型的成员,并且重写了getDesc和cost方法

/**
 * 装饰者
 */
public class Decorator extends Drink {
    private Drink drink; // 持有一个Drink的实例

    public Decorator(Drink drink) {
        // 聚合关系
        this.drink = drink;
    }

    @Override
    public String getDesc() {
        return desc + " && " + drink.getDesc();
    }

    @Override
    public float cost() {
        // 获取自己的价格 + drink的价格
        return super.getPrice() + drink.cost();
    }
}

调味品类(牛奶、巧克力、豆浆):继承自装饰类,可以设置自己的描述和单价

public class Milk extends Decorator {
    public Milk(Drink drink) {
        super(drink);
        setDesc(" 牛奶 ");
        setPrice(2.0f);
    }
}
public class Chocolate extends Decorator {
    public Chocolate(Drink drink) {
        super(drink);
        setDesc(" 巧克力 ");
        setPrice(3.0f);
    }
}
public class Soy extends Decorator {
    public Soy(Drink drink) {
        super(drink);
        setDesc(" 豆浆 ");
        setPrice(1.5f);
    }
}

现在咖啡厅开业了,顾客想买这样的咖啡:2份巧克力 + 1份牛奶的美式咖啡,我们应该如何满足客户的需求呢?

public class CoffeeBar {
    public static void main(String[] args) {
        // 用装饰者模式实现的咖啡厅下订单:2份巧克力 + 1份牛奶的LongBlack
        // 1. 点一份 LongBlack
        Drink order = new LongBlack();
        System.out.println("单点LongBlack,费用:" + order.cost() + ", 描述:" + order.getDesc());
        // 2. 加入一份牛奶
        order = new Milk(order);
        System.out.println("LongBlack+Milk,费用:" + order.cost() + ", 描述:" + order.getDesc());
        // 3. 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("LongBlack+Milk+Chocolate,费用:" + order.cost() + ", 描述:" + order.getDesc());
        // 4. 再加入一份巧克力
        order = new Chocolate(order);
        System.out.println("LongBlack+Milk+Chocolate*2,费用:" + order.cost() + ", 描述:" + order.getDesc());
    }
}

运行结果如下:

单点LongBlack,费用:5.0, 描述: 美式咖啡 
LongBlack+Milk,费用:7.0, 描述: 牛奶  &&  美式咖啡 
LongBlack+Milk+Chocolate,费用:10.0, 描述: 巧克力  &&  牛奶  &&  美式咖啡 
LongBlack+Milk+Chocolate*2,费用:13.0, 描述: 巧克力  &&  巧克力  &&  牛奶  &&  美式咖啡 

这就是装饰者模式实现的咖啡厅点单,客户可以随意的进行咖啡种类和调味品的组合,而且也没有类爆炸的现象发生,后续扩展咖啡种类或者调味品,只需要继承Coffee类或者装饰类即可。


装饰者模式类图

4 IO流中的装饰者模式

以字节输入流为例,所有字节输入流的基类都是InputStream,它的子类有FileInputStream、StringBufferInputStream、ByteArrayInputStream等,除此之外,还有一个装饰类FilterInputStream,在这个类中聚合了一个InputStream类型的成员:


FilterInputStream类

FilterInputStream的子类有BufferedInputStream、DataInputStream、LineNumberInputStream等,这些子类可以将缓冲功能、数据类型功能、行号记录功能附加到InputStream流中,开发者可以按需使用这些装饰类来更加方便地使用IO流。


装饰者模式在IO流中的应用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容