3.装饰者模式

1.什么是装饰者模式?

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

2.结合需求案例设计理解此模式

四郎咖啡店因为市场反应很好,所以扩张速度非常快,但因为它的扩张速度太快了,其现有的订单系统无法满足市场需求了,所以他们决定更新订单系统,来满足他们的饮料供应需求。

那么如何进行更新呢? 首先需要了解目前的设计现状

2.1 目前设计现状

他们的系统中原来的类的设计是这样的:
在这里插入图片描述

源代码如下:
Beverage.java

package DecoratorPattern.first.base;
public abstract class Beverage {
    
    private String description;
    
    public abstract double cost();

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

HouseBlend.java

package DecoratorPattern.first.coffee;

import DecoratorPattern.first.base.Beverage;

/**
 * HouseBlend 定价 5元
 */
public class HouseBlend extends Beverage {
    @Override
    public double cost() {
        return 5;
    }
}

DarkRoast.java

package DecoratorPattern.first.coffee;

import DecoratorPattern.first.base.Beverage;

/**
 * DarkRoast 定价 10元
 */
public class DarkRoast extends Beverage {
    @Override
    public double cost() {
        return 10;
    }
}

Decaf.java

package DecoratorPattern.first.coffee;

import DecoratorPattern.first.base.Beverage;

/**
 * Decaf 定价 8元
 */
public class Decaf extends Beverage {
    @Override
    public double cost() {
        return 8;
    }
}

测试:

package DecoratorPattern.first;

import DecoratorPattern.first.coffee.Decaf;
import DecoratorPattern.first.coffee.HouseBlend;

public class MainTest {
    public static void main(String[] args) {
        System.out.println("用户A 点一杯HouseBlend 价格为:"+new HouseBlend().cost());
        System.out.println("用户B 点一杯 Decaf 价格为:"+ new Decaf().cost());
    }
}

在这里插入图片描述

2.2 首次需求

现在因为业务扩张,除了可以购买不同种类的咖啡外,还可以要求在其中加入各种调料,例如 牛奶 Milk, 豆浆 Soy,
摩卡 Mocha等等。 并且咖啡店需要根据加入调料的不同进行加收费用。

2.2.1 第一版设计方案

员工A收到这个需求后,觉得很简单,于是他仅用了半个小时就给出了自己的方案:将现有的咖啡类和所有调料排列组合新建所有可能的饮料类,UML如下:
在这里插入图片描述

依次类推,后面新增的话还是根据这个思路增加类。

员工B看到后,不经感叹到: 这简直是无脑堆砌, 你最好不要给经理看到你这设计,不然你极有可能需要去逛招聘网站了。很明显,你在为自己创造一个维护恶梦,加入牛奶的价钱上涨或下降,想想你该怎么应对!!

于是员工A接纳了员工B的意见,仔细思考后,他给出了第二版方案

2.2.2 第二版设计方案

员工A发现,如果将调料维护在基类Beverage,也许会好很多:
在这里插入图片描述

将调料作为饮料的boolean属性,计算价格时,根据设置的值来进行具体的计算,这个方案似乎可行,下面我们来实现看看:

package DecoratorPattern.second.base;
public abstract class Beverage {
    private String description;
    private boolean milk;//定价1块
    private boolean soy;//定价2块
    private boolean mocha;//定价3块
    private boolean whip;//定价4块

    public Beverage(boolean milk,boolean soy,boolean mocha,boolean whip){
        this.milk = milk;
        this.soy = soy;
        this.mocha = mocha;
        this.whip = whip;
    }

    public double cost(){
        double addMoney = 0;
        if(hasMilk()) addMoney+=1;
        if(hasMocha()) addMoney+=3;
        if(hasSoy()) addMoney+=2;
        if(hasWhip()) addMoney+=4;
        return addMoney;
    }


    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

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

    public void setSoy(boolean soy) {
        this.soy = soy;
    }

    public void setMocha(boolean mocha) {
        this.mocha = mocha;
    }

    public void setWhip(boolean whip) {
        this.whip = whip;
    }

    public boolean hasMilk() {
        return milk;
    }

    public boolean hasSoy() {
        return soy;
    }

    public boolean hasMocha() {
        return mocha;
    }

    public boolean hasWhip() {
        return whip;
    }
}

然后编写具体的饮料类:

package DecoratorPattern.second.coffee;

import DecoratorPattern.second.base.Beverage;

/**
 * Decaf 定价 8元
 */
public class Decaf extends Beverage {

    public Decaf(boolean milk,boolean soy,boolean mocha,boolean whip){
        super(milk,soy,mocha,whip);
    }

    @Override
    public double cost() {
        return 8+super.cost();
    }
}
package DecoratorPattern.second.coffee;


import DecoratorPattern.second.base.Beverage;

/**
 * HouseBlend 定价 5元
 */
public class HouseBlend extends Beverage {

    public HouseBlend(boolean milk, boolean soy, boolean mocha, boolean whip) {
        super(milk, soy, mocha, whip);
    }

    @Override
    public double cost() {
        return 5+super.cost();
    }
}
package DecoratorPattern.second.coffee;
import DecoratorPattern.second.base.Beverage;
/**
 * DarkRoast 定价 10元
 */
public class DarkRoast extends Beverage {
    public DarkRoast(boolean milk, boolean soy, boolean mocha, boolean whip) {
        super(milk, soy, mocha, whip);
    }

    @Override
    public double cost() {
        return 10+super.cost();
    }
}

用上面这种设计我们进行使用时确实可以避免繁多的类编写:

package DecoratorPattern.second;
import DecoratorPattern.second.coffee.Decaf;
public class MainTest {
    public static void main(String[] args) {
        //普通的Decaf
        System.out.println(new Decaf(false,false,false,false).cost());
        //加牛奶的Decaf
        System.out.println(new Decaf(true,false,false,false).cost());
    }
}

在这里插入图片描述

很明显,我们可以在使用时传入boolean参数来为饮料主体添加调料即可,在计算时会自己根据参数计算出正确的价钱,这次应该既满足了需求,也比较容易维护了吧。

2.3 第二次需求

在实际的服务过程中,有的用户比较喜欢某一个调料,比如他想加双倍的牛奶,这时,我们的系统好像满足不了顾客了,于是业务部门想要你使其满足支持n倍的调料的饮料价格计算。

员工A思考了一会儿,说,这个可以实现,我们只需要修改Beverage中的调料从boolean类型为int类型的不就行了。
员工B: 那样做是不是就意味着,所有继承它的子类都要进行修改? 如果它的子类很多的话,岂不是噩梦!!

的确,员工A没有考虑到这一点,这时他仿佛记起了一个原则: 类应该对扩展开放,对修改关闭。
于是,他带着思考,接着寻求更佳的设计。

既然调料会出现无法预计的使用方式,那么干脆就将每个调料作为一个个体类进行设计,这样也许会好一点,员工A尝试着设计,我们可以以饮料为主体,然后在运行时用调料来给饮料附加价钱计算,就像代理模式那样,动态的为饮料的cost方法增强。
在这里插入图片描述

首先,基类代码如下:

package DecoratorPattern.third.base;

/**
 * 饮料抽象类
 */
public abstract class Beverage {
    public String description = "Unknown Beverage";
    public String getDescription(){
        return description;
    }
    public abstract double cost();
}

其次,调料基类如下:

package DecoratorPattern.third.base;

/**
 * 调料抽象类
 */
public abstract class CondimentDecorator extends Beverage{
    public abstract String getDescription();
}

然后就是饮料类:(这里只展示一个)

package DecoratorPattern.third.coffee;

import DecoratorPattern.third.base.*;

/**
 *首选咖啡实体类
 */
public class HouseBlend extends Beverage {
    @Override
    public double cost() {
        return 10;
    }
    public HouseBlend(){
        description = "House Blend Coffee";
    }
}

然后就是调料类:

package DecoratorPattern.third.smallCase;

import DecoratorPattern.third.base.Beverage;
import DecoratorPattern.third.base.CondimentDecorator;

/**
 * Mocha调料
 */
public class Mocha extends CondimentDecorator {
    Beverage beverage;
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return 2+ beverage.cost();
    }
    @Override
    public String getDescription() {
        return beverage.getDescription()+ ", Mocha";
    }
}

下面我们进行测试:

package DecoratorPattern.third;


import DecoratorPattern.third.coffee.HouseBlend;
import DecoratorPattern.third.smallCase.Mocha;
import DecoratorPattern.third.base.Beverage;

/**
 * 装饰者模式:动态的将责任附到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
 */
public class StarBuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage2 = new HouseBlend();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        System.out.println(beverage2.cost());

    }
}

在这里插入图片描述

成功给HouseBlend饮料加了双倍的Mocha调料并计算出了价格。

这,就是装饰者模式:
装饰者模式动态的将责任附加到对象上。 如果要扩展功能,装饰者提供了比继承更有弹性的替代方案。

装饰者模式是继承和组合的中和作用发生奇妙反应的产物。

在JDK中的I/O流中用到了装饰者模式,感兴趣的同学可以自己去看一下源码,看看是否和这个模式匹配。

项目地址:
https://gitee.com/yan-jiadou/design-mode/blob/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/src/main/java/DecoratorPattern/StarBuzzCoffee.java

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小牧之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值