• 设计模式学习笔记 - 装饰者模式



    一、咖啡订单问题


    问题:要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。使用面向对象来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以点单品咖啡与调料的组合。

    • 咖啡种类(单品咖啡):Espresso(意大利浓咖啡)、LongBlack(美式咖啡)、Decaf(无因咖啡)。

    • 调料:Milk(牛奶)、Soy(豆浆)、Chocolate(巧克力)。

    二、方案1(传统方式)

    • 方案1类图:
      在这里插入图片描述

    • 方案1的分析:
      (1)Drink是一个抽象类,表示饮料。
      (2)description属性是对咖啡的描述,,比如咖啡的名字。
      (3)cost() 方法就是计算费用,在Drink类中是一个抽象方法。
      (4)Espresso、LongBlack、Decaf是单品咖啡, 继承Drink抽象类,并实现cost方法。
      (5)EspressoAddMilk、EspressoAddMilkSoy、EspressoAddMilkSoyChocolate是单品咖啡与调料的组合,单品咖啡与调料的组合有非常多种。

    • 方案1出现的问题:
      方案1的设计会存在很多类,当增加一个单品咖啡或者调料,类的数量就会倍增,会出现类爆炸。

    三、方案2(方案1的改进)


    方案1由于咖啡单品与调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多,从而提高项目的维护性。

    • 方案2的类图:
      在这里插入图片描述

    • 方案2的分析:

    方案2可以控制类的数量,不至于造成很多的类,但是在增加或者删除调料种类时,代码的维护量很大。建议使用装饰者模式。

    四、装饰者模式简介


    装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp原则)。

    装饰者模式就像打包一个快递(主体和包装):主体(被装饰者Component),比如陶瓷、衣服 。包装(装饰者Decorator),比如报纸、塑料泡沫、纸板、木板。

    装饰者模式原理:
    在这里插入图片描述
    类图对象分析:

    • Component 主体:比如Drink抽象类。
    • ConcreteComponent 具体的主体:比如各个单品咖啡。
    • Decorator 装饰者:比如Decorator调料类。
    • ConcreteDecorator 具体的装饰者:比如各种具体的调料。

    在Component主体与ConcreteComponent具体主体之间,如果ConcreteComponent类很多的话,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类。

    五、方案3(装饰者模式)

    • 方案3的类图:
      在这里插入图片描述
    • 加两份巧克力与一份牛奶的美式咖啡(LongBlack)的结构如下:
      在这里插入图片描述

    图中的一份Milk包含了LongBlack,一份Chocolate包含了 Milk+LongBlack,最后一份Chocolate包含了Chocolate+Milk+LongBlack。不管是什么形式的 单品咖啡+调料 的组合,通过递归方式可以方便的组合和维护。

    • 实现代码:
    package com.etc.design.decorator;
    
    public abstract class Drink {
    	// 描述
    	public String des;
    	// 费用
    	private float price = 0.0f;
    	public String getDes() {
    		return des;
    	}
    	public void setDes(String des) {
    		this.des = des;
    	}
    	public float getPrice() {
    		return price;
    	}
    	public void setPrice(float price) {
    		this.price = price;
    	}
    	//计算费用的抽象方法,子类来实现
    	public abstract float cost();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    package com.etc.design.decorator;
    
    public class Coffee  extends Drink {
    	@Override
    	public float cost() {
    		return super.getPrice();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.etc.design.decorator;
    
    public class LongBlack extends Coffee {
    	public LongBlack() {
    		setDes(" 美式咖啡 ");
    		setPrice(5.0f);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.etc.design.decorator;
    
    public class Espresso extends Coffee {
    	public Espresso() {
    		setDes(" 意大利咖啡 ");
    		setPrice(6.0f);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.etc.design.decorator;
    
    public class DeCaf extends Coffee {
    	public DeCaf() {
    		setDes(" 无因咖啡 ");
    		setPrice(1.0f);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.etc.design.decorator;
    
    public class Decorator extends Drink {
    	// 组合关系
    	private Drink obj;
    	public Decorator(Drink obj) {
    		this.obj = obj;
    	}
    	@Override
    	public float cost() {
    		// getPrice-获取装饰者(当前类)的价格
    		// obj.getDes()-获取被装饰者的费用
    		return super.getPrice() + obj.cost();
    	}
    	@Override
    	public String getDes() {
    		// obj.getDes()-获取被装饰者的信息
    		return des + " " + getPrice() + " && " + obj.getDes();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    package com.etc.design.decorator;
    
    // 具体的Decorator(调味品)
    public class Chocolate extends Decorator {
    	public Chocolate(Drink obj) {
    		super(obj);
    		setDes(" 巧克力 ");
    		setPrice(3.0f); // 调味品的价格
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.etc.design.decorator;
    
    // 具体的Decorator(调味品)
    public class Milk extends Decorator {
    	public Milk(Drink obj) {
    		super(obj);
    		setDes(" 牛奶 ");
    		setPrice(2.0f);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    package com.etc.design.decorator;
    
    // 具体的Decorator(调味品)
    public class Soy extends Decorator{
    	public Soy(Drink obj) {
    		super(obj);
    		setDes(" 豆浆  ");
    		setPrice(1.5f);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.etc.design.decorator;
    
    public class Client {
    	public static void main(String[] args) {
    		// 装饰者模式下的订单:2份巧克力+1份牛奶的美式咖啡
    		// 1. 点一份 美式咖啡
    		Drink order = new LongBlack();
    		System.out.println("美式咖啡 费用=" + order.cost());
    		System.out.println("美式咖啡 描述=" + order.getDes());
    		// 2. 美式咖啡加入一份牛奶
    		order = new Milk(order);
    		System.out.println("美式咖啡 加入1份牛奶 费用=" + order.cost());
    		System.out.println("美式咖啡 加入1份牛奶 描述= " + order.getDes());
    		// 3. 美式咖啡加入一份巧克力
    		order = new Chocolate(order);
    		System.out.println("美式咖啡 加入1份牛奶 加入1份巧克力 费用=" + order.cost());
    		System.out.println("美式咖啡 加入1份牛奶 加入1份巧克力 描述= " + order.getDes());
    		// 4. 美式咖啡加入一份巧克力
    		order = new Chocolate(order);
    		System.out.println("美式咖啡 加入1份牛奶 加入2份巧克力 费用=" + order.cost());
    		System.out.println("美式咖啡 加入1份牛奶 加入2份巧克力 描述= " + order.getDes());
    		System.out.println("===========================");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    六、装饰者模式在JDK应用的源码分析


    Java的IO结构中使用了装饰者模式,DataInputStream等类是被装饰者,而FilterInputStream是装饰者。

    • 源码:
    public abstract class InputStream implements Closeable {
        ......
    }
    
    • 1
    • 2
    • 3
    public class FileInputStream extends InputStream {
        ......
    }
    
    • 1
    • 2
    • 3
    public class FilterInputStream extends InputStream {
        /**
         * The input stream to be filtered.
         */
        protected volatile InputStream in;
        
        protected FilterInputStream(InputStream in) {
            this.in = in;
        }
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public class DataInputStream extends FilterInputStream implements DataInput {
        public DataInputStream(InputStream in) {
            super(in);
        }
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 测试例子:
    package com.etc.uml.demo;
    
    import java.io.DataInputStream;
    import java.io.FileInputStream;
    
    public class Decorator {
    	public static void main(String[] args) throws Exception{
    		//说明:
    		//1. InputStream 是抽象类, 类似方案3中的Drink类。
    		//2. FileInputStream 是被装饰者,是InputStream的子类,类似方案3中LongBlack、Espresso、Decaf。
    		//3. FilterInputStream 是装饰者,是InputStream的子类,类似方案3中的Decorator。在FilterInputStream类中有一个InputStream类的成员属性in,即 FilterInputStream 含有 被装饰者。
    		//4. DataInputStream 是具体的修饰者,是FilterInputStream的子类,类似方案3中的Milk、Soy、Chocolate。
    		DataInputStream dis = new DataInputStream(new FileInputStream("D:\\abc.txt"));
    		System.out.println(dis.read());
    		dis.close();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    Java 实现多风扇
    设计模式-组合模式
    创建数据库表提示Working outside of application context.
    Jpg格式如何转成gif格式动图?简单一招搞定gif制作
    Docker swarm 通过 docker-compose 部署应用
    2022.11.1 固体物理
    做项目中都有哪些误区?
    如何通过EasyCVR接口监测日志观察平台拉流情况?
    Android Jetpack Compose之UI的重组和自动刷新
    什么是软件测试?零基础入门知识要点总结篇,5分钟带你快速了解
  • 原文地址:https://blog.csdn.net/qq_42141141/article/details/127838798