《Head First 设计模式》笔记3

装饰者模式(Decorate)

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

设计原则四:类应该对扩展开放,对修改关闭。

如果使用过 Python,应该听过装饰器,虽然概念有点不同,但都是通过动态添加的方式给对象扩展功能。

栗子

星巴克的订单系统系统中有个饮料抽象类 Beverage,店内的饮料都必须继承该类:

abstract class Beverage {
    // 饮料描述,比如咖啡、牛奶
    protected String description;

    public String getDescription() {
        return this.description;
    }

    // 饮料价钱
    public abstract float cost();
}

其中一些饮料:

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

    @Override
    public float cost() {
        return 3.2f;
    }
}

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

    @Override
    public float cost() {
        return 4.6f;
    }
}

// ...

提需求

现在,客户想要在买的饮料里加点调味料,比如买了杯咖啡,要加点牛奶、豆浆、巧克力等。这就需要订单系统在统计饮料价格时加上调味料的价格。

错误示范1

每种可能的调味料的饮料都新建一个类:

class CoffeeWithMilk extends Beverage {
    @Override
    public float cost() {
        return 7.9f;
    }
}

class MilkWithCoffee extends Beverage {
    @Override
    public float cost() {
        return 8.2f;
    }
}

// ....

很明显,调味料有很多,饮料也有很多,如果只加一种调味料,那搭配起来也有非常多的可能,如果都新建一个类,那么就会造成“类爆炸”。而且这些调味料的价格如果发生变化,就要将涉及的饮料都修改一遍,严重违反了设计原则中的“将变化与不变化的代码分开”。

错误示范2

既然违反了变化的原则,那么尝试把这些变化的调味料都放在饮料抽象类 Beverage 中?

abstract class Beverage {
    protected String description;
    // 是否添加了某些调味料
    protected boolean milk;
    protected boolean coffee;

    public String getDescription() {
        return this.description;
    }

    // 计算好添加的调味料价格,让子类直接调用
    public float cost() {
        float sum = 0;
        if (hasCoffee()) {
            sum += 3.2f;
        }
        if (hasMilk()) {
            sum += 4.6f;
        }
        return sum;
    }

    protected boolean hasCoffee() {
        return this.coffee;
    }

    protected void setCoffee(boolean coffee) {
        this.coffee = coffee;
    }

    protected boolean hasMilk() {
        return this.milk;
    }

    protected void setMilk(boolean milk) {
        this.milk = milk;
    }
}

饮料只要设置好添加的调味料,最后计算下自身花费就好了:

class Coffee extends Beverage {
    @Override
    public float cost() {
        return super.cost() + 3.2f;
    }
}

class Milk extends Beverage {
    @Override
    public float cost() {
        return super.cost() + 4.6f;
    }
}

调用:

public class Main {
    public static void main(String[] args) {
        Coffee coffee = new Coffee();
        // 加点牛奶
        coffee.setMilk(true);
        System.out.println(coffee.cost());
    }
}

现在没有了“类爆炸”,就算调料价格发生变化,只要修改下抽象类就好了,但是真的没问题了吗?

有问题,而且挺多的:
1. 如果出现新的调味料,就需要在抽象类添加新的布尔值成员,cost 方法也需要添加新的判断,还要分别新增一个 setter 和一个 getter,如果是删除一个调味料呢?
2. 对于有些饮料来说,某些调味料并不能加(会拉肚子的!),比如冰红茶 + 牛奶?
3. 如果客户要一杯咖啡,加两份牛奶呢?

满足需求

不改变原有的饮料抽象和具体的饮料:

abstract class Beverage {
    protected String description;

    public String getDescription() {
        return this.description;
    }

    public abstract float cost();
}
class Coffee extends Beverage {
    public Coffee() {
        description = "Coffee";
    }

    @Override
    public float cost() {
        return 3.2f;
    }
}

添加调味料(condiment)装饰者:

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

调味料继承调味料装饰者:

class Milk extends CondimentDecorator {
    Beverage beverage;

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

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + milk";
    }

    @Override
    public float cost() {
        return .50f + beverage.cost();
    }
}

class Mocha extends CondimentDecorator {
    Beverage beverage;

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

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + mocha";
    }

    @Override
    public float cost() {
        return .20f + beverage.cost();
    }
}

测试

public class Main {
    public static void main(String[] args) {
        Beverage coffee1 = new Coffee();
        // 不需要调味料
        System.out.println(coffee1.getDescription() + " $" + coffee1.cost());

        Beverage coffee2 = new Coffee();
        // 加牛奶和摩卡
        coffee2 = new Milk(coffee2);
        coffee2 = new Mocha(coffee2);
        System.out.println(coffee2.getDescription() + " $" + coffee2.cost());

        Beverage coffee3 = new Coffee();
        // 加3份牛奶和2份摩卡
        coffee3 = new Milk(coffee3);
        coffee3 = new Milk(coffee3);
        coffee3 = new Milk(coffee3);
        coffee3 = new Mocha(coffee3);
        coffee3 = new Mocha(coffee3);
        System.out.println(coffee3.getDescription() + " $" + coffee3.cost());
    }
}

输出:
Coffee 3.2Coffee+milk+mocha 3.2 C o f f e e + m i l k + m o c h a 3.9
Coffee + milk + milk + milk + mocha + mocha $5.0999994

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值