通过模拟咖啡馆的点单系统来剖析装饰者模式的使用

参考:https://blog.csdn.net/gududedabai/article/details/81989196

一)、传统的点单系统构建,每一个种类的咖啡都定义一个类

  • 弊端:
  1. 如果为每一种混合咖啡都定义一个类,那么,会产生很多的类对象。
  2. 混合咖啡的价格是在单品咖啡的基础上的,如若某一单品咖啡的价格
    发生改变,那么就要修改与之关联的所有的混合咖啡的价格。

    /**
    • 抛转引玉
    • --:咖啡订单系统
    •  1.咖啡店售卖四款基础咖啡
    •    Espressio, ShortBlack, LongBlack, Decaf
    •  2.可以在四款基础咖啡的基础上加入调料,例如Milk,Soy、Chocolate  ,组成混合咖啡
    •  3.每款咖啡都共同的description属性,和getDecription(),指料明加入的调料,cost()计算咖啡所需的价格。
      */
    /**
    • 咖啡的共同属性
      */
      public abstract class Coffee {
      /**
      • 描述咖啡加入的调料+单品种类
        */
        private String description;

      public Coffee() {
      }

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

      public Coffee(String description) {
      this.description = description;
      }

      /**
      • 打印购买的咖啡信息
        */
        void getDescription(){
        System.out.println(description);
        }
      /**
      • 计算咖啡所需花费的价格
      • @return
        */
        public abstract int CoffeePrice();
        }

单品咖啡:

/**
 * 低糖咖啡
 */
public class Decaf extends Coffee {
    Decaf(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        return 30;
    }
}

单品咖啡:

/**
 * 浓咖啡
 */
public class Espressio extends Coffee {
    Espressio(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        return 10;
    }
}

单品咖啡:

/**
 * 黑咖啡
 */
public class LongBlack extends Coffee{
    LongBlack(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        return 20;
    }
}

单品咖啡:

/**
 * 浓缩咖啡
 */
public class ShortBlack extends Coffee{
    ShortBlack(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        return 15;
    }
}

混合咖啡:

/**
 * 组合咖啡: 无糖+牛奶
 */
public class DecafAndMilk extends Coffee{
    DecafAndMilk(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        return 35;
    }
}

售卖咖啡:

   1)、在new 对象时就指名加入的调料,getDescription()时打印咖啡种类和加

    入的调料,直接调用CoffeePrice()返回咖啡的价格。

2)、需要为每一种混合咖啡都创建一个咖啡对象

/**
 * 售卖咖啡
 *  使用传统方式来构建售卖咖啡的类:
 *    --: 所有的混合咖啡都实现了超类Coffee
 *        此时,出现了一个问题
 *        ---》1.因为调料的种类很多,调料与调料之间的组合方式也很多,这时咖啡类的数量就会增多。
 *            2.因为所有的混合咖啡都是在单品咖啡的基础上构建的,当单品咖啡的价格发生了调整,所有
 *               与单品咖啡相关的混合咖啡的价格都要进行调整。
        */
public class SaleCoffee {
    public static void main(String[] args) {
        Coffee coffee = new Decaf("无糖咖啡");
        //打印咖啡的种类和价格
        coffee.getDescription();
        System.out.println(coffee.CoffeePrice());

        //无糖+牛奶的咖啡
        Coffee coffee1 = new DecafAndMilk("无糖咖啡:+牛奶");
        coffee1.getDescription();
        System.out.println(coffee1.CoffeePrice());
    }
}

结果:

无糖咖啡
30
无糖咖啡:+牛奶
35

二)、将调料声明在超类中,在单品咖啡的基础上加入调料,只需定义单品咖啡类即可

  • 好处
    1.减少了组合咖啡类的定义,通过判断hasXxx()可以在四个单品咖啡的 基础上加调料,即可以通过四个单品类来得到很多的组合咖啡
  • 弊端
  1. 当需要加入一种调料时,需要修改超类中的代码,这样违反了代码的开闭原则, 一旦修改了代码就会有产生bug的风险。
  2. 当用户需要加两份调料,如:加入两份牛奶时不能通过hasMilk()来计算咖啡的价格。

所有咖啡的超类:

将所有的调料以boolean的形式声明在超类中,并通过hasXxx()来判断是否加

    入调料以及计算咖啡的价格。

import com.sun.xml.internal.ws.util.StringUtils;

/**
 * 构建第二种形式的咖啡超类
 *   --: 一开始就给定了咖啡的调料
 */
public abstract class Coffee {
    /**
     * 描述咖啡的种类和调料
     */
    private String description;

    /**
     * 将咖啡的调料内置在超类中
     */
    private boolean milk;

    private boolean soy;

    private boolean chocolate;

    public Coffee(String description) {
        this.description = description;
    }

    public Coffee() {
    }


    /**
     * 判断是否加了牛奶
     * @return
     */
    public Boolean hashMilk(){
       return milk;
    }

    /**
     * 判断是否加了豆浆
     * @return
     */
    public Boolean hashSoy(){
       return soy;
    }

    /**
     * 判断是否加了巧克力
     * @return
     */
    public Boolean hashChocolate(){
        return chocolate;
    }

    /**
     * 根据咖啡的种类和调料计算咖啡的价格
     */
    public abstract int CoffeePrice();

    public void getDescription() {
        System.out.println(description);
    }

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

    public void setMilk(boolean milk) {
        this.milk = milk;
        if(milk == true) {
            setDescription("+牛奶");
        }
    }

    public void setSoy(boolean soy){
        this.soy = soy;
        if(soy == true){
            setDescription("+豆浆");
        }
    }

    public void setChocolate(boolean chocolate) {
        this.chocolate = chocolate;
        if(chocolate == true) {
            setDescription("+chocolate");
        }
    }
}

单品咖啡:

通过hasXxx()来计算最终的价格。

**
 * 低糖咖啡
 */
public class Decaf extends Coffee {
    Decaf(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        //单品低糖咖啡的价格为30
        int cost = 30;
        if(this.hashMilk()){
            cost = cost + 5;
        }
        if(this.hashChocolate()){
            cost = cost + 10;
        }
        if(this.hashSoy()){
            cost = cost + 2;
        }
        return cost;
    }
}

单品咖啡:

/**
 * 浓咖啡
 */
public class Espressio extends Coffee {
    Espressio(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        //单品浓咖啡的价格为10
        int cost = 10;
        if(this.hashMilk()){
            cost = cost + 5;
        }
        if(this.hashChocolate()){
            cost = cost + 10;
        }
        if(this.hashSoy()){
            cost = cost + 2;
        }
        return cost;
    }
}

单品咖啡:

/**
 * 黑咖啡
 */
public class LongBlack extends Coffee{
    LongBlack(String description){
        super(description);
    }

    /**
     * 判断当前种类的咖啡是否有加入调料,若有则加入调料的价格
     * @return
     */
    @Override
    public int CoffeePrice() {
        //单品黑咖啡的价格为20
        int cost = 20;
        if(this.hashMilk()){
            cost = cost + 5;
        }
        if(this.hashChocolate()){
            cost = cost + 10;
        }
        if(this.hashSoy()){
            cost = cost + 2;
        }
        return cost;
    }
}

单品咖啡:

/**
 * 浓缩咖啡
 */
public class ShortBlack extends Coffee{
    ShortBlack(String description){
        super(description);
    }
    @Override
    public int CoffeePrice() {
        //单品浓缩咖啡的价格为15
        int cost = 15;
        if(this.hashMilk()){
            cost = cost + 5;
        }
        if(this.hashChocolate()){
            cost = cost + 10;
        }
        if(this.hashSoy()){
            cost = cost + 2;
        }
        return cost;
    }
}

售卖咖啡:

/**
 * 将所有的调料放在超类中
 *   --:减少了组合咖啡类的定义,通过判断hasXxx()可以在四个单品咖啡的基础上加调料,即可以通过四个单品类来得到很多的组合咖啡
 *
 *   弊端:
 *    --:1.当需要加入一种调料时,需要修改超类中的代码,这样违反了代码的开闭原则,一旦修改了代码就会有产生bug的风险。
 *        2.当用户需要加两份调料,如:加入两份牛奶时不能通过hasMilk()来计算咖啡的价格。
 *
 */
public class SaleCoffee {
    public static void main(String[] args) {
        //先选择咖啡单品
        Coffee coffee = new Decaf("无糖咖啡:");
        //组合咖啡: 无糖 + 牛奶
        coffee.setMilk(true);
        coffee.setChocolate(false);
        coffee.getDescription();
        System.out.println(coffee.CoffeePrice());
    }
}




结果:

无糖咖啡:+牛奶
35

三)你要喝什么味的咖啡?

使用装饰者模式来实现不同咖啡种类的搭配:

(装饰者模式的模型)

主体接口类:

被装饰类:

    装饰类:

           具体装饰类1;具体装饰类2;具体装饰类3;

           具体装饰类具有叠加效果

主体接口类:

/**
 * 实现装饰者模式的逻辑:
 *       1.公共接口类:
 *       --:需要一个公共接口类
 *         该接口定义了被装饰类的主要逻辑方法,被装饰者和装饰者分别去实现或继承这个接口
 *       2.被装饰类:
 *         被装饰者实现公共接口类,并对接口方法做具体实现
 *       3.装饰类:
 *        装饰类接收被装饰类对象,调用被装饰类的方法
 *       4.具体装饰类
 *         继承装饰类,做具体的装饰逻辑实现
 */

/**
 * 公共接口类:
 *   --: 定义需要装饰的接口方法以及公共的对象属性
 */
public abstract class Coffee {
    private String description;

    private double price;

    /**
     * 获取咖啡的价格
     * @return
     */
    public abstract double getCoffeePrice();

    public abstract String getCoffeeDescription();

    public String getDescription() {
        return description;
    }

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

    public double getPrice() {
        return price;
    }

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

被装饰类:

/**
 * 被装饰类:
 *  --:该类为咖啡单品类,可以为该咖啡加各种调料,然后计算咖啡的价格以及获取咖啡的种类和所加的调料
 */
public class Decaf extends Coffee{

    /**
     * 因为是单品咖啡,所以对象刚创建时价格和种类就已经确定了
     */
    Decaf(){
        setPrice(30);
        setDescription("低糖:");
    }
    @Override
    public double getCoffeePrice() {
        return this.getPrice();
    }

    @Override
    public String getCoffeeDescription() {
        return this.getDescription();
    }
}

装饰类:

/**
 * 装饰类:
 *  --:接收被装饰对象,调用被装饰对象的方法
 */
public class Decorator extends Coffee{
    /**
     * 被装饰对象
     */
   private Coffee coffee = null;
    /**
     * 一初始化就有传入一个被装饰对象
     */
    Decorator(Coffee coffee){
        this.coffee = coffee;
    }

    @Override
    public double getCoffeePrice() {
        //使用被装饰者的功能
        return coffee.getCoffeePrice();
    }

    @Override
    public String getCoffeeDescription(){
        return coffee.getCoffeeDescription();
    }
}

具体装饰类:

/**
 * 具体的装饰者实现类
 *   --:继承装饰类
 */
public class Milk extends Decorator{
    Milk(Coffee coffee) {
        super(coffee);
        setPrice(10);
        setDescription("+牛奶");
    }
    @Override
    public double getCoffeePrice() {
        //使用被装饰者的功能
        return super.getCoffeePrice()+this.getPrice();
    }

    @Override
    public String getCoffeeDescription(){
        return super.getCoffeeDescription()+this.getDescription();
    }
}

具体装饰类:

public class Chocolate extends Decorator {
    Chocolate(Coffee coffee) {
        super(coffee);
        setPrice(15);
        setDescription("+chocolate");
    }
    @Override
    public double getCoffeePrice() {
        //使用被装饰者的功能
        return super.getCoffeePrice()+this.getPrice();
    }

    @Override
    public String getCoffeeDescription(){
        return super.getCoffeeDescription()+this.getDescription();
    }

}

生产咖啡:

public class SaleCoffee {
    public static void main(String[] args) {
        //加双份牛奶
        Decorator decorator = new Decorator(new Milk(new Milk(new Decaf())));
        System.out.println(decorator.getCoffeeDescription());
        System.out.println(decorator.getCoffeePrice());
    }
}

结果:

低糖:+牛奶+牛奶
50.0

四)、装饰者模式举例二、你要吃什么味道的鸡腿堡?

参考: https://blog.csdn.net/jason0539/article/details/22713711

主题接口类:

/**
 * 使用汉堡店买汉堡的例子来实现装饰者模式
 *   --:汉堡可以选择加生菜、火腿、沙拉、番茄酱
 */
public abstract class Hamburger {
    /**
     * 汉堡名字
     */
    private String name;

    /**
     * 汉堡的价格
     */
    private double price;

    /**
     * 制作汉堡
     */
    public abstract String product();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

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

被装饰类:

/**
 * 基础汉堡
 */
public class ChickenBurger extends Hamburger{
    ChickenBurger(){
        setName("鸡腿堡");
        setPrice(25);
    }
    @Override
    public String product() {
       return this.getName()+": " + this.getPrice();
    }
}

装饰类:

public class Decorator extends Hamburger {
    Hamburger hamburger;
    Decorator(Hamburger hamburger){
        this.hamburger = hamburger;
    }

    public Decorator() {
    }

    @Override
    public String product() {
        return hamburger.product();
    }
}

具体装饰类:

/**
 * 装饰类,给汉堡加生菜
 */
public class Lettuce extends Decorator{
    Lettuce(Hamburger hamburger) {
        this.hamburger = hamburger;
        setName(hamburger.getName()+"+生菜");
        setPrice(hamburger.getPrice()+2);
    }
    @Override
    public String product(){
        return getName()+": "+getPrice();
    }
}

具体装饰类:

/**
 * 给火腿加鸡蛋
 */
public class Age extends Decorator{
    Age(Hamburger hamburger) {
        this.hamburger = hamburger;
        setName(hamburger.getName()+"+鸡蛋");
        setPrice(hamburger.getPrice()+1.5);
    }

    @Override
    public String product() {
        return getName()+": "+getPrice();
    }
}

制作汉堡:

public class SaleHamburger {
    public static void main(String[] args) {
        //鸡腿堡 +生菜 + 鸡蛋
        Decorator decorator = new Decorator(new Lettuce(new Age(new ChickenBurger())));
        //原味鸡腿堡
        Decorator decorator1 = new Decorator(new ChickenBurger());
        //鸡腿堡 +生菜
        Decorator decorator2 = new Decorator(new Lettuce(new ChickenBurger()));
        //鸡腿堡 +鸡蛋
        Decorator decorator3 = new Decorator(new Age(new ChickenBurger()));

        System.out.println(decorator.product());
        System.out.println(decorator1.product());
        System.out.println(decorator2.product());
        System.out.println(decorator3.product());
    }
}

结果:

鸡腿堡+鸡蛋+生菜: 28.5
鸡腿堡: 25.0
鸡腿堡+生菜: 27.0
鸡腿堡+鸡蛋: 26.5

五)、装饰者模式在java Jdk中的应用

装饰者模式应用在java Jdk api文档中的IO流机制

I) :主题接口类:InputStream

   II):被装饰类:

         FileInputStream; StringBufferInputStream, ByteArrayInputStream

        装饰类:

         FilterInputStream

                III): 具体实现类:

                         BufferInputStream; DataInputStream; LineNumberInputStream

使用jdk设计好的装饰者模式,将流对象中的小写字母转为大写字母

具体装饰类:

 继承FilterInpustream

/**
 * java的Io流机制就使用了装饰模式
 *    InputStream: 接口类
 *    FileInputStream: StringBufferInputStream: ByteArrayInPutStream : 被装饰类
 *    FilterInputStream: 装饰类接口
 *       --: 具体装饰类
 *           BufferInputStream: DataInputStream: LineNumberInputStream
 *
 */

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 实现一个将流中的小写字母转为大写字母的装饰者对象
 */
public class UpperCaseInputStream extends FilterInputStream {

    protected UpperCaseInputStream(InputStream in) {
        super(in);
    }

    @Override
    //读取流,将流中的小写字母转为大写字母
    public int read() throws IOException//单字符的读
    {
        int c=super.read();//这个super.read()就是调用上面super(in);的主题对象
        return c==-1?c:Character.toUpperCase((char)(c));
    }

    @Override
    public int read(byte[] b,int offset,int len) throws IOException//多字符的读
    {
        int result=super.read(b,offset,len);
        for(int i=0;i<result;i++)
        {
            b[i]=(byte)Character.toUpperCase((char)(b[i]));
        }
        return result;
    }
}

使用类:

public class Test {
    public static void main(String[] args) {
        int c;
        try {
            InputStream in = new UpperCaseInputStream(new BufferedInputStream(
                    new FileInputStream("F:\\test.txt")));
            while((c=in.read())>=0)
            {
                System.out.print((char)c);

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}
01-21 14:18