装饰器模式

一、概述

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

二、解决问题

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

 

 如上图,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

posted @ 2019-08-22 16:10  西凤楼  阅读(185)  评论(0编辑  收藏  举报
如果,您认为阅读这篇博客让您有些收获, 如果,您希望更容易地发现我的新博客,不妨关注一下。因为,我的写作热情也离不开您的肯定支持。 感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客。 因为有小孩,兼职卖书,路过的朋友有需要低价购买图书、点读笔、纸尿裤等资源的,可扫最上方二维码,质量有保证,价格很美丽,欢迎咨询!