zoukankan      html  css  js  c++  java
  • 【设计模式】装饰者模式

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

    这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

    基本介绍

    • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

    • 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

    • 何时使用:在不想增加很多子类的情况下扩展类。

    • 如何解决:将具体功能职责划分,同时继承装饰者模式。

    • 关键代码:
      • 1、Component 类充当抽象角色,不应该具体实现。
      • 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
    • 应用实例:
      • 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
      • 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
    • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

    • 缺点:多层装饰比较复杂。

    • 使用场景:
      • 1、扩展一个类的功能。
      • 2、动态增加功能,动态撤销。

    注意事项:可代替继承。

    概括

    装饰者模式定义

    1. 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
    2. 这里提到的动态的将新功能附加到对象和 ocp 原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会。

    类图:

    装饰者模式原理,角色分析

    1. 装饰者模式就像打包一个快递
      主体:比如:陶瓷、衣服 (Component) //被装饰者
      包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
    2. Component 主体:比如类似后面的 Drink
    3. ConcreteComponent:具体的主体,比如陶瓷,比如前面的各个单品咖啡
    4. Decorator 装饰者:装饰主体,比如盒子,比如后面的各调料。

      在如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。
      解释:陶瓷放在盒子中,盒子在外层装饰陶瓷,
      装饰者套娃,一层包一层就是装饰(继承+聚合)
      (聚合是为了装饰(new xxx(xxx)),而继承是为了套娃(new xxx(new xxx)))

    我的理解

    在不改变原有实体的情况下,对原有实体进行装饰。如 圆→红色的圆。

    应用实例

    星巴克咖啡订单项目(咖啡馆):

    1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
    2. 调料:Milk、Soy(豆浆)、Chocolate
    3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
    4. 使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。

    使用传统方式

    方案 1-解决星巴克咖啡订单项目

    类图:

    方案 1-解决星巴克咖啡订单问题分析

    1. Drink 是一个抽象类,表示饮料
    2. des 就是对咖啡的描述, 比如咖啡的名字
    3. cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
    4. Decaf 就是单品咖啡,继承 Drink,并实现 cost
    5. Espress && Milk 就是单品咖啡+调料,这个组合很多
    6. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸

    方案 2-解决星巴克咖啡订单(好点)

    1. 前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)

    2. 说明: milk,soy,chocolate 可以设计为 Boolean,表示是否要添加相应的调料.

    方案 2-解决星巴克咖啡订单问题分析

    1. 方案 2 可以控制类的数量,不至于造成很多的类
    2. 在增加或者删除调料种类时,代码的维护量很大
    3. 考虑到用户可以添加多份 调料时,可以将 hasMilk 返回一个对应 int
    4. 考虑使用 装饰者 模式

    使用装饰者模式

    类图:

    装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack

    装饰者模式咖啡订单项目应用实例
    代码实现

    Decorator

    package com.atguigu.decorator;
    
    public class Decorator extends Drink { 
        private Drink obj;
    
        public Decorator(Drink obj) { //聚合
            // TODO Auto-generated constructor stub 
            this.obj = obj;
        }
    
    
        @Override
        public float cost() {
            // TODO Auto-generated method stub
            // getPrice 自己价格
            return super.getPrice() + obj.cost();
        }
    
    
        @Override
        public String getDes() {
            // TODO Auto-generated method stub
            // obj.getDes() 输出被装饰者的信息
            return des + " " + getPrice() + " && " + obj.getDes();
        }
    }

    Drink

    package com.atguigu.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();
    }

    Coffee

    package com.atguigu.decorator;
    
    public class Coffee extends Drink {
    
        @Override
        public float cost() {
            // TODO Auto-generated method stub 
            return super.getPrice();
        }
    }

    DeCaf

    package com.atguigu.decorator;
    
    public class DeCaf extends Coffee {
    
        public DeCaf() {
            setDes(" 无因咖啡 "); 
            setPrice(1.0f);
        }
    }

    Espresso

    package com.atguigu.decorator;
    
    public class Espresso extends Coffee {
    
        public Espresso() {
            setDes(" 意大利咖啡 "); setPrice(6.0f);
        }
    }

    LongBlack

    package com.atguigu.decorator;
    
    public class LongBlack extends Coffee {
    
        public LongBlack() {
            setDes(" longblack ");
            setPrice(5.0f);
        }
    }

    ShortBlack

    package com.atguigu.decorator;
    
    public class ShortBlack extends Coffee{
    
        public ShortBlack() { 
            setDes(" shortblack "); 
            setPrice(4.0f);
        }
    }

    Chocolate

    package com.atguigu.decorator;
    
    //具体的 Decorator, 这里就是调味品
    public class Chocolate extends Decorator {
    
        public Chocolate(Drink obj) { 
            super(obj);
            setDes(" 巧克力 ");
            setPrice(3.0f); // 调味品 的价格
        }
    }

    Milk

    package com.atguigu.decorator;
    
    public class Milk extends Decorator {
    
        public Milk(Drink obj) { 
            // TODO Auto-generated constructor stub
            super(obj);
            setDes(" 牛 奶 "); setPrice(2.0f);
        }
    }

    Soy

    package com.atguigu.decorator;
    
    public class  extends Decorator{
    
        public Soy(Drink obj) {
            // TODO Auto-generated constructor stub
            super(obj);
            setDes(" 豆浆 "); 
            setPrice(1.5f);
        }
    }

    CoffeeBar

    package com.atguigu.decorator;
    
    public class CoffeeBar {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            // 装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack
    
            // 1.  点一份 LongBlack
            Drink order = new LongBlack();
            System.out.println("费用 1=" + order.cost());
            System.out.println("描述=" + order.getDes());
    
            // 2. order 加入一份牛奶
            order = new Milk(order);
            System.out.println("order 加入一份牛奶 费用 = " + order.cost());
            System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
    
            // 3. order 加入一份巧克力
            order = new Chocolate(order);
            System.out.println("order 加入一份牛奶 加入一份巧克力 费用 = " + order.cost());
            System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
    
            // 3. order 加入一份巧克力
            order = new Chocolate(order);
            System.out.println("order 加入一份牛奶 加入 2 份巧克力   费用 = " + order.cost());
            System.out.println("order 加入一份牛奶 加入 2 份巧克力 描述 = " + order.getDes());
    
    
            System.out.println("===========================");
    
    
            Drink order2 = new DeCaf();
            System.out.println("order2 无因咖啡 费用 = " + order2.cost());
            System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
    
            order2 = new Milk(order2); 
            System.out.println("order2 无因咖啡 加入一份牛奶 费用 = " + order2.cost());
            System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
        }
    }

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

    Java 的 IO 结构,FilterInputStream 就是一个装饰者

    源码说明

    package com.atguigu.jdk;
    
    import java.io.DataInputStream; 
    import java.io.FileInputStream; 
    import java.io.InputStream;
    
    public class Decorator {
    
        public static void main(String[] args) throws Exception{
            // TODO Auto-generated method stub
    
            //说明
            //1. InputStream 是抽象类,类似我们前面讲的 Drink
            //2. FileInputStream 是  InputStream 子类,类似我们前面的 DeCaf, LongBlack
            //3. FilterInputStream 是 InputStream 子类,类似我们前面 的 Decorator 修饰者
            //4. DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等
            //5. FilterInputStream 类 有 protected volatile InputStream in; 即 含被装饰者
            //6. 分析得出在 jdk 的 io 体系中,就是使用装饰者模式
    
            DataInputStream dis = new DataInputStream(new FileInputStream("d:\abc.txt")); 
            System.out.println(dis.read());
            dis.close();
        }
    }
  • 相关阅读:
    用魔数防范文件上传攻击
    nginx http跳转到https
    tengine安装
    版本标记说明
    nginx基于域名的虚拟主机 反向代理配置实例
    非ROOT用户启动Tomcat
    使用druid连接池的超时回收机制排查连接泄露问题
    Jenkins入门系列之
    centos7 关闭SELINUX 防火墙
    mac安装IE浏览器
  • 原文地址:https://www.cnblogs.com/blknemo/p/13258262.html
Copyright © 2011-2022 走看看