zoukankan      html  css  js  c++  java
  • 装饰器模式

    一、概述

      装饰器模式动态地将责任附加到对象上。想要扩展功能,装饰者提供了有别于继承的另一种选择。简单描述就是包装对象,让对象提供新的行为。

    二、解决问题

      当一个类想要获得一个行为,我们会想到面向对象四大特性之一的继承,继承能够让子类从父类中获得行为,实现很好的代码复用。但这种继承而来的行为是在编译时静态决定的,而且所有的子类都会继承相同的行为。如果我们想要扩展对象的行为,就要创建一个子类来修改父类的方法(也就是覆盖父类行为),每扩展一个行为就要创建一个子类,这样会带来很多问题。第一,如果需要扩展的行为有很多,则子类就要创建无数多个(导致类爆炸)。第二、如果子类中的行为需要依赖某个成员变量,当这个成员变量发生改变,子类的代码就要修改(代码维护噩梦)

     如上图,getCost()方法获取茶的价钱,我们每新上市一种茶饮料就要创建一个子类;当茶的某些配料价格发生变化时,我们要修改子类代码。

    装饰者模式就是解决以上的问题的,它利用组合的做法扩展对象行为,可以在运行时动态地进行扩展,写新的代码添加新功能,而无须修改现有代码。

     三、结构类图

    四、成员角色

       1.抽象组件(Component)角色:定义一个将要接收附加责任的类,即继承该抽象类的类都有了装饰和被装饰的能力。

      2.具体组件(ConcreteComponent)角色:可以被动态加上新行为,被装饰者修饰的类。

      3.装饰者(Decorator)角色:装饰者抽象类,继承该类都具有装饰者的能力。

      4.具体装饰者(ConcreteDecorator)角色:为具体组件添加新行为。

    五、应用实例

    下面我们用奶茶饮料价格的例子来解析装饰者的用法,买奶茶的时候我们可以可以买纯奶茶,也可以添加珍珠,咖啡等配料,但价格肯定不一样

      //首次我们创建抽象组件,也就是茶饮料

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class Tea {
        String description = "Unknown Tea";
        //茶的描述
        public String getDescription(){
            return description;
        }
        //返回茶的价钱
        public abstract double getCost();
    }

      //接着创建抽象配料,就是装饰者

    1
    2
    3
    4
    public abstract class CondimentDecorator extends Tea{
        //所有调料必须重新实现描述方法
        public abstract String getDescription();
    }

       //创建奶茶,具体组件,继承抽象组件,被装饰的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MilkTea extends Tea{
     
        public MilkTea(){
            description = "奶茶";
        }
         
        @Override
        public double getCost() {
            //返回奶茶价格
            return 3.0;
        }
     
    }

      //创建珍珠配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Pearl extends CondimentDecorator{
        //持有对所装饰对象的引用
        private Tea tea;
         
        public Pearl(Tea tea){
            this.tea = tea;
        }
         
        @Override
        public String getDescription() {
            return "珍珠," +  tea.getDescription() ;
        }
     
        @Override
        public double getCost() {
            //把茶的价格加上珍珠的价格,得到最后结果
            return 1.0 + tea.getCost();
        }
     
    }

      //创建咖啡配料,就是具体的装饰者,继承抽象配料,实现对奶茶的装饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Coffee extends CondimentDecorator{
        //持有对所装饰对象的引用
        private Tea tea;
         
        public Coffee(Tea tea){
            this.tea = tea;
        }
         
        @Override
        public String getDescription() {
            //加上咖啡后的描述
            return "咖啡," + tea.getDescription();
        }
     
        @Override
        public double getCost() {
            //茶的价格加上咖啡的价格,算出结果
            return 2.0 + tea.getCost();
        }
    }

      //测试装饰者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class TestDecorator {
        public static void main(String[] args){
            //创建一杯纯奶茶,不需要加调料,打印出描述和价格
            Tea tea = new MilkTea();
            System.out.println(tea.getDescription() + " 价格为:¥" + tea.getCost());
             
            //创建加调料的奶茶
            Tea tea2 = new MilkTea();
            //加上一份珍珠
            tea2 = new Pearl(tea2);
            //再加一份珍珠
            tea2 = new Pearl(tea2);
            //加上一份咖啡调料
            tea2 = new Coffee(tea2);
             
            //打印加了调料的奶茶
            System.out.println(tea2.getDescription() + " 价格为:¥" + tea2.getCost());
        }
    }

      运行结果:

    装饰者在Java I/O中的使用

      Java I/O 是使用装饰者模式最典型的例子,看下面InputStream类图可知

     六、优点和缺点

      1.优点

      (1)、利用组合和委托动态地扩展行为,而无须修改现有代码。

      (2)、可以用多个装饰者包装一个对象,得到不同的对象组合,从而使被包装的类的功能更加强大。

      (3)、装饰者和被装饰者都具有相同的超类,在需要被装饰者的场合,可以用装饰过的对象替代原被装饰者。

      (4)、装饰者和被装饰者是相互独立的,我们可以根据需要增加和改变具体组件和具体装饰者,最后通过组合方式使用它们,符合“开闭原则”。

      (5)、避免了通过继承方式扩展对象功能所带来的灵活性差,子类无限扩张的问题(类爆炸)。

      2.缺点

      (1)、导致设计中出现很多小对象,如果过度使用,会让程序变得复杂。

      (2)、装饰链过多会使程序效率低,也增加了使用和排错的难度。

    七、使用场景

      1、当需要动态得扩展对象行为,或者说是动态地增加功能时使用。

      2、当使用继承方式对系统功能扩展受到限制时,或者继承不利于系统扩展和维护时。

    八、总结

      1、装饰者和被装饰对象有相同的超类型。

      2、可以用一个或者多个装饰者包装一个对象。

      3、可以在任何时候,使用不同的装饰者组合来装饰对象。

      4、装饰者可以在被装饰者的行为前面、后面加上自己的行为,甚至取代被装饰者的行为,达到特定的目的。

    本文转自: https://www.cnblogs.com/jenkinschan/p/5683099.html

  • 相关阅读:
    洛谷 P1850 换教室(期望dp)
    简单异或 && 洛谷 P1469 找筷子 && 洛谷 P3908 数列之异或
    2020 CSP-J复赛题解
    2018 ICPC 南京 D Country Meow(模拟退火|三分)
    佩尔方程
    块速幂/光速幂
    1436F
    反Nim游戏
    P1447 [NOI2010]能量采集(莫比乌斯反演)
    P3768 简单的数学题 (莫比乌斯反演+杜教筛)
  • 原文地址:https://www.cnblogs.com/xifenglou/p/11394963.html
Copyright © 2011-2022 走看看