装饰器模式(从放弃到入门)

@(设计模式)

前面介绍了两篇设计模式,策略模式和观察者模式,其实自己也是在学习阶段,感觉收益很大。所以想继续分享,把java中的23中设计模式都总结一遍,在以后才能在实践中灵活运用。感兴趣的童鞋可以看看前面分享的两篇:
策略模式
观察者模式

前面两篇都是上来就是例子,需求,我想改变一下套路,今天先介绍装饰器的理论结构,再说例子。还是要再声明:例子来自于《HeadFirst 设计模式》,推荐大家看看原书,写得很浅显易懂。

理论知识

《装饰器模式(从放弃到入门)》

实际问题

今天的例子是一个 coffe 的例子,相信大家都去星巴克喝过咖啡,或者奶茶,我们在买coffe的时候,首先选择一个基本的coffe类型,比如 卡布奇洛,然后添加各种佐料:摩卡,豆浆,蒸奶等。基本类型是基础价,佐料又要宁外算钱。(真是聪明)

现在当前的系统设计是:
《装饰器模式(从放弃到入门)》

本来想自己画UML图,发现自己画也一样,还损失了书上一些重要信息,所以直接盗图好了,大家不要介意。
Beverage: 饮料,抽象类,getDescription() 返回描述(类型,佐料…),cost() 抽象方法,每种饮料话费不一样,所以子类自己去实现。
然后派生了4中饮料的子类: HoseBlend, DarkRoast, Decaf, Espresso(尼玛,我都没喝过),分别实现 cost方法,返回价格。

代码很简单,这里就不贴了,然后这个时候,如果饮料有100种,呵呵,这种设计类图就是这样:
《装饰器模式(从放弃到入门)》

类爆炸!这种设计,明显重用率太低,好吧,换种思路,我们把所有的佐料的放到公共父类中,让所有子类都拥有所有的佐料,只是在类中判断到底加没加,例如这样:

《装饰器模式(从放弃到入门)》

代码:

Beverage .java

public abstract class Beverage {
    protected String description;

    protected boolean milk;
    protected boolean soy;
    protected boolean mocha;
    protected boolean whip;

    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

HoseBlend.java

public class HoseBlend extends Beverage {

    public HoseBlend() {
        description = "HoseBlend";

        milk = true;
        soy = false;
        mocha = false;
        whip = false;
    }

    public double cost() {
        double money = 10.0;
        if (milk)
            money += 2.0;
        if (soy)
            money += 3.0;
        if (mocha)
            money += 3.0;
        if (whip)
            money += 2.0;
        return money;
    }
}

使用:

Beverage hoseBlend = new HoseBlend()
System.out.println(hoseBlend.getDescription())
System.out.println(hoseBlend.cost())

其他类省略了,当然这里写得不规范,每种佐料的价格应该写成常量,这里直接用了数字,这里不是重点。这样的类设计出来,我们可以用一段话来描述:一个抽象的Beverage类,里面包含了很多佐料,子类决定是否添加这些佐料,并且计算初始价格和佐料价格。

与前一种不同的是,这种更加规范,父类决定了所有的佐料和价格,只是你填不填加,自己决定。突然想到了一种更好的方法,为何不让Beverage去计算价格呢?

Beverage.java

public abstract class Beverage {
    protected String description;
    protected double money;

    public Beverage(){
        this.description = "unknown beverage";
        this.money = 10.0;
    }

    public String getDescription() {
        return description;
    }

    public double cost(){
        return money;
    }

    public void addMilk() {
        this.money += 2.0;
    }

    public void addSoy(){
        this.money += 3.0;
    }

    public void addMocha(){
        this.money += 3.0;
    }

    public void addWhip(){
        this.money += 2.0;
    }
}

添加4中 addXXX() 方法,然后计算money,将计算价格留给父类,子类只需要添加佐料:

HoseBlend .java

public class HoseBlend extends Beverage {
    public HoseBlend() {
        description = "HoseBlend";
        addMilk();
    }
}

感觉这样写重用可以更好,并且子类工作也更少了。呵呵,书上没写,自己瞎想的。但是上面的这种设计,当遇到添加一种佐料时,都必须修改Beverate类,在设计模式原则中有一个非常重要的原则,就是开闭原则:对扩展开放,对修改关闭。上面的几种设计都存在一定的问题。我们来看看今天的主角,装饰器模式,怎么来完成。

装饰器模式

先来看一张形象的图:

《装饰器模式(从放弃到入门)》

最里层的 DarkRoast 是饮料类型外面添加一层 Mocha, 再外层添加 Whip。最终cost() 就从最外层,一直调用到最里层,累加得到价格,呵呵,是不是有点像递归,对多态的递归,看看类结构:

《装饰器模式(从放弃到入门)》

当然这是在最初,我们类爆炸那个例子中的结构扩展的:
Beverage依然是那个抽象类,依然有4种饮料继承与它。不同的是,多了一个 CondimentDecorator,继承于 Beverage,然后4种佐料都继承与 CondimentDecorator,并都包含一个 beverage 的引用,表示自己装饰的对象。

好了,看看代码:

Beverage .java 还是长这样

public abstract class Beverage {
    protected String description;

    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

HouseBlend .java 第一种饮料,最低10.0元

public class HouseBlend extends Beverage {

    public HouseBlend(){
        description = "HoseBlend";
    }

    public double cost() {
        return 10.0;
    }
}

DarkRoast.java 第二种饮料,最低12.0元

public class DarkRoast extends Beverage {

    public DarkRoast(){
        description = "DarkRoast";
    }

    public double cost() {
        return 12.0;
    }
}

CondimentDecorator .java 让子类都重写getDescription() 方法

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

Milk.java : 牛奶,每加一份2.0 元

public class Milk extends CondimentDecorator {

    Beverage beverage;

    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Milk";
    }

    public double cost() {
        return this.beverage.cost() + 2.0;
    }
}

Mocha.java 摩卡,每份3.0元

public class Mocha extends CondimentDecorator {

    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Mocha";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

Soy.java 酱油,每份3.0元

public class Soy extends CondimentDecorator {

    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Soy";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

好了,看看我们怎么调用:

public class Main {
    public static void main(String[] args) {
        Beverage b1 = new HouseBlend();
        System.out.println(b1.getDescription() + " $" + b1.cost());

        Beverage b2 = new DarkRoast();
        b2 = new Mocha(b2);
        b2 = new Soy(b2);
        b2 = new Milk(b2);
        System.out.println(b2.getDescription() + " $" + b2.cost());
    }
}

输出:

HoseBlend $10.0
DarkRoast,Mocha,Soy,Milk $20.0

总结

  1. 解耦合了吧,饮料和佐料分开,想怎么加怎么加,如果要添加新饮料或者佐料,只需继续添加,而无需修改以前的结构。这就是对扩展开放,对修改关闭
  2. 我前面提到了一句话:递归多态,哈哈哈,自己瞎编的!为什么会出现这种结果,如果你对多态熟悉的话,就很好理解了,看上面代码的 b2:
    • 为什么 Milk 可以赋值给 Beverage , 因为 Milk 的父类继承于 Beverage
    • b2调用 cost() 是调用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一个b2,及 Soy, Soy调用cost(), 及调用 Mocha.cost() + 3.0 … , 知道最后调用 beverage 的 cost() , 是不是很像递归。

再一句话概括装饰者模式吧:

装饰者模式,就是装饰者(Docorator)需要继承与被装饰者(Component),并且持有被装饰者的引用(Component),从而可以通过复写,在原来 Component 的基础上对方法做一定的修饰。

    原文作者:算法小白
    原文地址: https://juejin.im/entry/580b270d0bd1d00057e5e233
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞