设计模式03—装饰者模式

上一篇:《设计模式02——观察者模式》

1.需求来源

有一家咖啡店由于店面规模的迅速扩张,他们准备更新订单系统,来符合他们的饮料供应要求,原先设计如下:
在这里插入图片描述

购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶(Steamed Milk)、豆浆(Soy) .摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。
于是在此背景下我们引入了另外一个设计的原则:类应该对外扩展开放,对修改关闭
这样设计的好处就是设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。
下面我们的主角正式登场:
有以下几个角色:

  • 饮料:HouseBlend,DarkRoast,Decaf,Espresso
  • 调料:蒸奶(Steamed Milk),豆浆(Soy),摩卡(Mocha),奶泡(Whip)

那么我们接下来要做的就是调制一杯DarkRoast,用摩卡装饰,以奶泡装饰,然后调用cost方法并且依赖委托(delegate)将调料价钱加上去
通过上面一段话我们可以看到装饰者模式里有两个角色:装饰者也就是我们的调料,被装饰者即饮料,比如刚刚调制的DarkRoast
下面我们以装饰者构造饮料订单
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 装饰者和被装饰对象有相同的超类型。
  • 可以用一个或多个装饰者包装一个对象。
  • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

2.定义装饰者模式

2.1 装饰者模式的说明

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
在这里插入图片描述

通过上面的类图我们可以看到CondimentDecorator扩展自Beverage类,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承达到“类型匹配”,而不是利用继承获得行为”。

装饰者需要和被装饰者(亦即被包装的组件)有相同的“接口”,因为装饰者必须能取代被装饰者。但是行为又是从哪里来的?
当我们将装饰者与组件组合时,就是在加入新的行为。所得到的新行为,并不是继承自超类,而是由组合对象得来的。而且因为使用对象组合,可以把所有饮料和调料更有弹性地加以混和与匹配,非常方便。
接下来我们把这些思路编程代码展示一下,先从Beverage类下手,也就是我们的超类

/**
 * 饮料的超类
 */
public abstract class Beverage {
    String description = "未知的饮料";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

Beverage很简单。让我们也来实现Condiment(调料)抽象类,也就是装饰者类吧:

/**
 * 调料
 * 首先,必须让Condiment Decorator 能够取代Beverage,所以将Condiment
 * Decorator扩展Beverage类。
 */
public abstract class CondimentDecorator extends Beverage{
    /**
     * 所有的调科装饰奢都必须重新实现jetDescription()方法
     * @return
     */
    public abstract String getDescription();
}

接下来开始编写饮料的代码
为了我们的思路更加清晰我在将饮料和调料分别有哪些在书写一遍

  • 饮料:HouseBlend(自酿咖啡),DarkRoast(黑咖啡),Decaf,Espresso(浓缩咖啡)
  • 调料:蒸奶(Steamed Milk),豆浆(Soy),摩卡(Mocha),奶泡(Whip)
    我们先从浓缩咖啡Espresso开始
/**
 * 浓缩咖啡
 * 首先.让Espresso扩展自Beveraze类.因为Espresso是
 * ━种饮科。
 */
public class Espresso extends Beverage {
    /**
     * 为了要设置饮料的描述,我们写了一个构造器。记住.descxiption实例变量继承自Beverage
     */
    public Espresso() {
        this.description = "浓缩咖啡";
    }

    /**
     * 最后,需要计算Espresso的价钱,现在不需墓啻调料的价钱
     * @return
     */
    @Override
    public double cost() {
        return 1.99;
    }
}

黑咖啡

/**
 * 饮料--黑咖啡
 */
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "黑咖啡";
    }

    @Override
    public double cost() {
        return 0.99;
    }
}

自酿咖啡

/**
 * 饮料--自酿咖啡
 */
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "自酿咖啡";
    }

    @Override
    public double cost() {
        return 1.0;
    }
}

接下来我们就具体的实现装饰者,先从摩卡下手

/**
 * 装饰者(调料)--摩卡
 */
public class Mocha extends CondimentDecorator {
    /**
     * 用一个实例变量记录饮料,也就是被装饰者。
     */
    Beverage beverage;

    /**
     * 想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮科当作构造器的参数,再由构造器将此饮科记录在实例变量中
     *
     * @param beverage
     */
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 要计算带Mocha饮科的价钱。首先把调用委托给被装饰对象,以计算价钱.然后再加
     * Mocha的价钱.得到最后结果。
     *
     * @return
     */
    @Override
    public double cost() {
        return 0.20 + this.beverage.cost();
    }

    /**
     * 我们希望叙述不只是描述饮料(例“OarkRoast”) ,
     * 而是完整比连调科都描述出来(例如“DarkRoast,Mocha”) 。
     * 所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙连(例“Mocha”)。
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Mocha";
    }
}

接下来依次实现豆浆(Soy),奶泡(Whip)

  • 1.Soy
      /**
      * 装饰者(调料)--豆浆
      */
      public class Soy extends CondimentDecorator {
          /**
          * 用一个实例变量记录饮料,也就是被装饰者。
          */
          Beverage beverage;
    
          /**
          * 想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮科当作构造器的参数,再由构造器将此饮科记录在实例变量中
          *
          * @param beverage
          */
          public Soy(Beverage beverage) {
              this.beverage = beverage;
          }
    
          /**
          * 要计算带Mocha饮科的价钱。首先把调用委托给被装饰对象,以计算价钱.然后再加
          * Mocha的价钱.得到最后结果。
          *
          * @return
          */
          @Override
          public double cost() {
              return 0.30 + this.beverage.cost();
          }
    
          /**
          * 我们希望叙述不只是描述饮料(例“OarkRoast”) ,
          * 而是完整比连调科都描述出来(例如“DarkRoast,Mocha”) 。
          * 所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙连(例“Mocha”)。
          *
          * @return
          */
          @Override
          public String getDescription() {
              return beverage.getDescription() + ",豆浆";
          }
      }
    
    
  • 2.Whip
      /**
      * 装饰者(调料)--奶泡
      */
      public class Whip extends CondimentDecorator {
          /**
          * 用一个实例变量记录饮料,也就是被装饰者。
          */
          Beverage beverage;
    
          /**
          * 想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮科当作构造器的参数,再由构造器将此饮科记录在实例变量中
          *
          * @param beverage
          */
          public Whip(Beverage beverage) {
              this.beverage = beverage;
          }
    
          /**
          * 要计算带Mocha饮科的价钱。首先把调用委托给被装饰对象,以计算价钱.然后再加
          * Mocha的价钱.得到最后结果。
          *
          * @return
          */
          @Override
          public double cost() {
              return 0.40 + this.beverage.cost();
          }
    
          /**
          * 我们希望叙述不只是描述饮料(例“OarkRoast”) ,
          * 而是完整比连调科都描述出来(例如“DarkRoast,Mocha”) 。
          * 所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙连(例“Mocha”)。
          *
          * @return
          */
          @Override
          public String getDescription() {
              return beverage.getDescription() + ",奶泡";
          }
      }
    

接下来我们开始下订单

public class StarBuckCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + "$" + beverage.cost());
        //用黑咖啡为原料添加--摩卡,奶泡做出一个新的饮料
        Beverage beverage2 = new DarkRoast();//原料--黑咖啡
        beverage2 = new Mocha(beverage2);//添加摩卡
        beverage2 = new Mocha(beverage2);//用第二个摩卡装饰它
        beverage2 = new Whip(beverage2);//添加奶泡
        System.out.println(beverage2.getDescription()
                + " $" + String.format("%.2f", beverage2.cost()));
        //调料为豆浆,摩卡,奶泡
        Beverage beverage3 = new HouseBlend();//原料
        beverage3=new Soy(beverage3);
        beverage3=new Mocha(beverage3);
        beverage3=new Whip(beverage3);
        System.out.println(beverage3.getDescription()
                + " $" + String.format("%.2f", beverage3.cost()));

    }
}

最终结果如下:
在这里插入图片描述

店主决定开始在菜单上加上咖啡的容量大小,供顾客可以选择小杯(tall)、中杯(grande) 、大杯(venti)。所以在Beverage类中加上了getSize()与setSize()。他们也希望调料根据咖啡容量收费,例如:小中大杯的咖啡加上豆浆,分别加收0.10、0.15、0.20美金。如何改变装饰者类应对这样的需求?
首先我们对超类Beverage进行更改

/**
 * 饮料的超类
 */
public abstract class Beverage {
    //小杯、中杯、大杯
    public enum Size {
        TALL, GRANDE, VENTI
    }

    ;
    Size size = Size.TALL;

    String description = "未知的饮料";

    public String getDescription() {
        return description;
    }

    public Size getSize() {
        return size;
    }
    public abstract double cost();
}

由于是杯子的大小只针对于豆浆这一个装饰者,所以我们对豆浆Soy类进行修改

/**
 * 装饰者(调料)--豆浆
 */
public class Soy extends CondimentDecorator {
    /**
     * 用一个实例变量记录饮料,也就是被装饰者。
     */
    Beverage beverage;

    /**
     * 想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮科当作构造器的参数,再由构造器将此饮科记录在实例变量中
     *
     * @param beverage
     */
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    /**
     * 要计算带饮科的价钱。首先把调用委托给被装饰对象,以计算价钱.然后再加
     * 豆浆的价钱.得到最后结果。
     * @return
     */
    @Override
    public double cost() {
        double cost = beverage.cost();
        switch (beverage.getSize()) {
            case TALL:
                cost += 0.10;
                break;
            case GRANDE:
                cost += 0.15;
                break;
            case VENTI:
                cost += 0.20;
                break;
        }
        return cost;
    }

    /**
     * 我们希望叙述不只是描述饮料(例“OarkRoast”) ,
     * 而是完整比连调科都描述出来(例如“DarkRoast,Mocha”) 。
     * 所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙连(例“Mocha”)。
     *
     * @return
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",豆浆";
    }
}

最后我们再次进行测试

public class StarBuckCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + "$" + beverage.cost());
        //用黑咖啡为原料添加--摩卡,奶泡做出一个新的饮料
        Beverage beverage2 = new DarkRoast();//原料--黑咖啡
        beverage2 = new Mocha(beverage2);//添加摩卡
        beverage2 = new Mocha(beverage2);//用第二个摩卡装饰它
        beverage2 = new Whip(beverage2);//添加奶泡
        System.out.println(beverage2.getDescription()
                + " $" + String.format("%.2f", beverage2.cost()));
        //调料为豆浆,摩卡,奶泡
        Beverage beverage3 = new HouseBlend();//原料
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println(beverage3.getDescription()
                + " $" + String.format("%.2f", beverage3.cost()));
        //和上面同样的配方,但是我们将杯子的大小改成中杯(默认是小杯)
        Beverage beverage3_1 = new HouseBlend();//原料
        beverage3_1.size = Beverage.Size.GRANDE;
        beverage3_1 = new Soy(beverage3_1);
        beverage3_1 = new Mocha(beverage3_1);
        beverage3_1 = new Whip(beverage3_1);
        System.out.print("将杯子大小改为GRANDE--中杯之后:");
        System.out.println(beverage3_1.getDescription()
                + " $" + String.format("%.2f", beverage3_1.cost()));

    }
}

运行结果:
在这里插入图片描述

3.装饰java.io类

在这里插入图片描述

我们完成下面这个功能:
当读取“I know the veeorator Pattern therefore I RuLE!”,装饰者会将它转成“i know the decorator patfern therefore i rule!”

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class InputTest {
    public static void main(String[] args) {
        int c;
        try {
            //设置FileInputStream,先用
            //BufferedInputStream装饰它,再用我们崭新的LowerCaseInputStream过滤装饰它。
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(
                    new FileInputStream("D:\\program\\java_program\\design-mode\\DesignMode\\src\\main\\java\\com\\zhiyi\\design\\装饰者模式\\test.txt")));
            while ((c = in.read()) >= 0) {//只用流来读取字符,一直到文件尾端。每读一个字符,就马上将它显示出来。
                System.out.print((char) c);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:
在这里插入图片描述

至此对于装饰者模式就结束啦

下一篇:《设计模式04—工厂模式》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ZNineSun

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

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

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

打赏作者

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

抵扣说明:

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

余额充值