• 装饰器模式 Decorator 结构型 设计模式 (十)


    引子

     
    image_5b91e046_4d0a
     
    image_5b91e046_bbf
     
     

     

    现实世界的装饰器模式

    大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介
    "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?"
    这句话会不会很耳熟,或者自己可能都说过呢?
     
    我们看看这句话到底表达了哪些含义呢?
    你应该可以看得到这两个基本角色
    1.手抓饼                                 核心角色
    2.配菜(鸡蛋/培根/香肠...)          装饰器角色
     

    你既然想要吃手抓饼,自然你是奔着手抓饼去的,对吧
    所以,你肯定会要一个手抓饼,至少是原味的
    然后可能根据你的口味或者喜好添加更多的配菜
    这个行为很自然,也很正常.
     
    如果是在代码的世界里面,你怎么描述:  顾客 购买 手抓饼     这一行为呢?  
    顾客Customer   顾客有方法buy  然后有一个手抓饼HandPancake,看起来是这样子的
    那么问题来了
    如何表示 加了鸡蛋的手抓饼,或者加了鸡蛋和培根的手抓饼呢?
     
    一种很可能方式是把他们都当成手抓饼的不同种类,也就是使用继承或者说实现类的形式
    那么我们有多少种手抓饼呢?
    原味手抓饼/加鸡蛋手抓饼/加鸡蛋加培根手抓饼/加鸡蛋加烤肠手抓饼/加鸡蛋加培根加烤肠手抓饼手抓饼/.......
    很显然,这就是数学中的组合,最终的个数跟我们到底有多少种配菜有关系
    如果按照这种思维方式,我们将会有无数个手抓饼类,而且如果以后多了一种配菜,类的个数将会呈现爆炸式的增长
    这是你想要的结果么?
     
    在现实世界里面,你会很自然的说 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?""
    那么为什么在程序世界里面,你却很可能说"老板,给我来一个加了鸡蛋加了培根的那种手抓饼" 呢?
     
     

    手抓饼代码示例

    手抓饼接口和具体的一家店铺提供的手抓饼

    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:手抓饼接口 描述抽象的手抓饼
    */
    public interface HandPancake {
    /**
    * 提供手抓饼
    */
    String offerHandPancake();
    /**计算手抓饼的价格
    * @return
    */
    Integer calcCost();
    }
    
    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description: Noteless 家的手抓饼
    */
    public class NotelessHandPancake implements HandPancake {
    /**
    * 提供noteless 家的手抓饼一份
    */
    @Override
    public String offerHandPancake() {
    return " noteless 家的手抓饼";
    }
    /**计算 noteless 家 一份手抓饼的价格
    * @return
    */
    @Override
    public Integer calcCost() {
    return 3;
    }
    }

    配菜抽象类(装饰器)

    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:装饰器类实现了手抓饼接口,具有了手抓饼的类型
    */
    public abstract class Decorator implements HandPancake{
    private HandPancake handPancake;
    Decorator(HandPancake handPancake){
    this.handPancake = handPancake;
    }
    /**提供手抓饼
    * @return
    */
    @Override
    public String offerHandPancake() {
    return handPancake.offerHandPancake();
    }
    
    /**提供手抓饼的价格
    * @return
    */
    @Override
    public Integer calcCost() {
    return handPancake.calcCost();
    }
    }

    具体的配菜(具体的装饰)

    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:培根
    */
    public class Bacon extends Decorator {
        Bacon(HandPancake handPancake){
            super(handPancake);
        }
    
        @Override
        public String offerHandPancake() {
            return super.offerHandPancake()+" 加培根";
        }
        @Override
        public Integer calcCost() {
            return super.calcCost()+4;
        }
    }
    
    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:鸡蛋
    */
    public class Egg extends Decorator {
        Egg(HandPancake handPancake){
            super(handPancake);
        }
        @Override
        public String offerHandPancake() {
            return super.offerHandPancake()+"加鸡蛋";
        }
        @Override
        public Integer calcCost() {
            return super.calcCost()+2;
        }
    }
    
    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:烤肠
    */
    public class Sausage extends Decorator {
        Sausage(HandPancake handPancake){
            super(handPancake);
        }
        @Override
        public String offerHandPancake() {
            return super.offerHandPancake()+" 加香肠";
        }
        @Override
        public Integer calcCost() {
            return super.calcCost()+3;
        }
    }
    
    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:青菜
    */
    public class Vegetable extends Decorator {
        Vegetable(HandPancake handPancake){
            super(handPancake);
        }
        @Override
        public String offerHandPancake() {
            return super.offerHandPancake()+" 加青菜";
        }
        @Override
        public Integer calcCost() {
            return super.calcCost()+1;
        }
    
    }

    顾客

    package decorator;
    /**
    * Created by noteless on 2018/9/6.
    * Description:顾客具有名字,然后购买手抓饼
    */
    public class Customer {
    private String name;
    Customer(String name){
    this.name = name;
    }
    
    public void buy(HandPancake handPancake){
      System.out.println(name+"购买了 : "+handPancake.offerHandPancake()+
      " 一份, 花了 : "+handPancake.calcCost()+"块钱~");
      System.out.println();
    }
    }

    测试类

    package decorator;
    
    /**
    * Created by noteless on 2018/9/6.
    * Description:
    * 手抓饼3块
    * Sausage 烤肠 3块
    * Bacon 培根 4块
    * Egg 鸡蛋2块
    * Vegetable 青菜 1块
    */
    
    public class Test {
    public static void main(String ...strings){
    
    //有一个顾客张三,他想吃手抓饼了,来了一个原味的
    Customer customerA = new Customer("张三");
    customerA.buy(new NotelessHandPancake());
    
    //有一个顾客李四,他想吃手抓饼了,他加了一根烤肠
    Customer customerB = new Customer("李四");
    customerB.buy(new Sausage(new NotelessHandPancake()));
    
    //有一个顾客王五,他想吃手抓饼了,他加了一根烤肠 又加了培根
    Customer customerC = new Customer("王五");
    customerC.buy(new Bacon(new Sausage(new NotelessHandPancake())));
    
    //有一个顾客王五的兄弟,他想吃手抓饼了,他加了培根 又加了烤肠
    Customer customerC1 = new Customer("王五的兄弟");
    customerC1.buy(new Sausage(new Bacon(new NotelessHandPancake())));
    
    //有一个顾客赵六,他想吃手抓饼了,他加了一根烤肠 又加了2份培根
    Customer customerD = new Customer("赵六");
    customerD.buy(new Bacon(new Bacon(new Sausage(new NotelessHandPancake()))));
    //有一个顾客 王二麻子,他想吃手抓饼了,特别喜欢吃青菜 来了三分青菜 Customer customerE = new Customer("王二麻子"); customerE.buy(new Vegetable(new Vegetable(new Vegetable(new NotelessHandPancake()))));
    //有一个顾客 有钱人 王大富 来了一个全套的手抓饼 Customer customerF = new Customer("王大富"); customerF.buy(new Egg(new Vegetable(new Bacon(new Sausage(new NotelessHandPancake()))))); } }
    我们有一个顾客Customer类,他拥有buy方法,可以购买手抓饼
    手抓饼接口为 HandPancake  具体的手抓饼为NotelessHandPancake
    然后提供了一个配菜类,这个配菜类的行为和手抓饼是一致的,在提供手抓饼的同时还能够增加一些额外的
    然后还有四个具体的配菜 培根 香肠 鸡蛋 青菜
     
    运行测试类,会算账的亲们,看看单价是否还对的上?
    image_5b91e046_7b49

    UML图

    懒得画了,IDEA自动生成的
    image_5b91e046_739c
     

     手抓饼装饰器模式中的根本

    上面的代码还是比较清晰的,如果你没办法仔细看进去的话,我们换一种思维方式来思考手抓饼的装饰器模式
     
    你可以这么理解:
    你过去手抓饼的摊位那边,你说老板来一个手抓饼,加培根,加鸡蛋
     
    摊主那边是这样子的:
    老板负责直接做手抓饼
    旁边站着漂亮的老板娘,手里拿着手抓饼的袋子,负责帮你装袋,你总不能直接用手拿饼,对吧
     
    接下来我们说下过程:
    老板马上就开始做手抓饼了,做好了之后,老板把手抓饼交给了旁边站着的老板娘
    老板娘在给装袋并且交给你之前
    把鸡蛋和培根放到了你的手抓饼里面
    然后又放到了包装袋子里面
    接着递给了你
     
    你说到底是老板娘手里包装好的手抓饼是手抓饼  还是老板做好的热气腾腾的是手抓饼呢?
     
    其实,老板做好的热气腾腾的手抓饼,正是我们上面提供出来的具体的手抓饼
    老板娘手里拿着的手抓饼包装袋来包装手抓饼,也是手抓饼,只不过是包装了下,这个就是装饰器的概念
     
    所以装饰器模式还有一个名字  包装器模式(Wrapper)
     
     
    解决问题的根本思路是使用组合替代了继承
    上面我们也进行了分析,继承会出现类的个数的爆炸式增长
    组合,不仅仅动态扩展了类的功能,而且还很大程度上减少了类的个数
    不过显然,如果你的装饰类过多,虽说比继承好很多,但是问题还是一样的,都会类过多
     
    根本:  是你还有你
     
    我们上面的类的结构中,装饰器包含一个手抓饼对象作为属性,他也实现了手抓饼接口
    所以我们说,是你还有你
    每次自己返回结果之前,都还会调用自己含有的对象的方法
     
    看下调用流程, 你说它的形式跟递归调用有什么区别?
    image_5b91e047_17fb
     
     

    面向对象中的适配器模式详解

    意图

    动态的给一个对象添加额外的职责,简单说,动态的扩展职责
    就增加功能来说,装饰器模式比生成子类要更加灵活
    所以装饰器模式主要解决继承子类爆炸增长的问题
     

    装饰器模式中的角色

    Component 抽象构建 装饰器模式中必然有一个最基本最原始的->
    接口/抽象类
    来充当抽象构建
    抽象的手抓饼    HandPancake
    ConcreteComponent 具体构建 
    是抽象构建的一个具体实现
    你要装饰的就是它
    具体某家店铺生产的手抓饼   NotelessHandPancake
    Decorator 装饰抽象类 一般是一个抽象类
    实现抽象构建
    并且必然有一个private变量指向Component 抽象构建
    配菜抽象类(装饰器)   Decorator
    ConcreteDecorator 具体的装饰类 必须要有具体的装饰角色
    否则装饰模式就毫无意义了
    具体的配菜(具体的装饰)    Bacon Egg  Vegetable Sausage
     
    image_5b91e047_1c47
    仔细体味下<是你 还有你>
    Decorator 是Component 还有Component
     
    OOP中的一个重要设计原则
    类应该对扩展开放,对修改关闭
    所谓修改就是指继承,一旦继承,那么将会对部分源代码具有修改的能力,比如覆盖方法,所以你尽量不要做这件事情
    扩展就是指的组合,组合不会改变任何已有代码,动态得扩展功能
     

    装饰器模式优点

    装饰类和被装饰类可以独立发展,而不会相互耦合
     
    Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,
    而Decorator也不用知道具体的构件
    装饰模式是继承关系的一个替代方案
    我们看装饰类Decorator,不管装饰多少层,他始终是一个Component,实现的还是is-a的关系,所以他是继承的一种良好替代方案
    如果设计得当,装饰器类的嵌套顺序可以任意,比如
    image_5b91e047_2ed8
    一定要注意前提,那就是你的装饰不依赖顺序

    装饰器模式缺点

    装饰器模式虽然从数量级上减少了类的数量,但是为了要装饰,仍旧会增加很多的小类
    这些具体的装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑
    装饰器模式虽然减少了类的爆炸,但是在使用的时候,你就可能需要更多的对象来表示继承关系中的一个对象
    多层的装饰是比较复杂,比如查找问题时,被层层嵌套,不容易发现问题所在
     

    装饰器模式使用场景

    当你想要给一个类增加功能,然而,却并不想修改原来类的代码时,可以考虑装饰器模式
    如果你想要动态的给一个类增加功能,并且这个功能你还希望可以动态的撤销,就好像直接拿掉了一层装饰物

    装饰器模式的简化变形

    装饰器模式是对继承的一种强有力的补充与替代方案,装饰器模式具有良好的扩展性
    再次强调,设计模式是一种思维模式,没有固定公式
    如果需要的话,可以进行简化
    如果省略抽象构建,装饰器直接装饰一个类的话,
    那么可以装饰器直接继承这个类

    image_5b91e047_7db9
    如果只有一个具体的装饰器类,那么可以省略掉 Decorator
    ConcreteDecorator 充当了ConcreteDecorator 和 Decorator的角色
    image_5b91e047_e8e 
     
     
    设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在!
     
     
     
  • 相关阅读:
    winform解析json API数据
    c#(winform)获取本地打印机
    winform程序post提交数据API
    C#关于panle重叠
    net面试总结的题目
    委托
    最实用JS 留着学习
    dev 控件获得所有的EFDEVGRID
    c#利用WebClient和WebRequest获取网页源代码的比较
    浅谈.Net WebService开发
  • 原文地址:https://www.cnblogs.com/noteless/p/9603041.html
Copyright © 2020-2023  润新知