《大雄学设计模式--(3)装饰者模式》

版权声明:本文为博主原创文章,欢迎转载,转载注明出处即可~~ https://blog.csdn.net/WuchangI/article/details/81289659

情景引入:

       转眼间,母亲节就快到了。虽然大雄在学习方面总是马马虎虎,但是对于母亲大人的节日还是记得一清二楚。这不,他就打算送他妈妈一个present,给妈妈一个惊喜。说做就做,他马上跑到购物街上挑选合适的礼物以及包装纸。也不知道逛了多久,他满身疲惫地走进了家门,然后躺在了他书房的榻榻米上。
    正在吃铜锣烧的哆啦A梦见状便问:”大雄,你又去干嘛了?见你挺累的样子,是不是去跑步了?”。
    ”不是,明天不是母亲节吗?我是去给妈妈挑礼物和包装纸了。哎~” , 大雄疲惫地说道。
    ”哦!这很好啊!你为什么唉声叹气的呢?”,哆啦A梦急切地问道。
    ”这。。。哎~ 我逛了好久,候选的礼物有围巾和面霜,候选的包装纸有包装纸A、包装纸B、包装纸C,而且我想给礼物包上一种或多种包装纸。现在问题就是,最终包好的礼物要花费我多少零用钱,我总是要对每种方案算一下,我数学又那么。。。哎,哆啦A梦,你能帮帮我吗?求求你了~~”,大雄央求道。
    ”这。。。”哆啦A梦陷入了沉思。。。


其中围巾(20元/条)、面霜(35元/瓶)、包装纸A(2元/张)、包装纸B(1元/张)、包装纸C(3元/张)。

一、简介

  • 装饰者模式(Decorator Pattern),属于结构型模式。
  • 该模式允许向一个已有的具体对象不断地添加新的功能,对该对象进行”包装”,同时不改变其结构。
  • 一般地,我们如果要给一个现有对象添加功能/职责,可以直接修改该对象并添加新的功能/职责,也可以继承该对象通过其子类来拓展其功能/职责,也可以使用对象组合的方式。第一种方法明显违反了开闭原则;而根据合成复用原则,我们应该尽量使用对象组合/聚合的方式,而不是使用继承来拓展和复用功能,况且,如果需要拓展的功能种类很多,这样会导致产生很多子类,增加了系统的复杂性,不易于管理,故第二种方法也不行;而装饰者模式本质就是对象的动态组合,其中动态是手段,保证了灵活性,组合是目的,以便拓展现有的功能/职责。
  • 该模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户端代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。



二、具体内容

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



三、结构组成

这里使用了继承方式不是为了拓展功能、职责,是为了保证具体装饰器类与具体构件类拥有相同的基类(Component),是为了继承类型!况且在装饰者模式中,新的功能/职责的添加是通过类与类的组合(has-a)的关系实现的,而不是通过继承方式实现的。

  • Component(抽象构件类/角色)
    一般是一个抽象类,是一组有着某种用途的类的基类,包含了这些类最基本的特性。它定义了一个对象接口,这使得可以给这些对象动态地添加职责。

  • ConcreteComponent(具体构件类/角色)
    是一个具体的对象,继承自Component类,一般是一个有实际用途的类,这个类就是以后要被装饰的对象,我们可以给它添加一些职责。

  • Decorator(装饰器类/角色)
    是一个抽象类,继承自Component类,(这样就使得我们可以”用某个装饰器去装饰具体构件对象,甚至是其他装饰器,而且可以装饰无限次,只要你喜欢,最终给具体构件对象添加了很多功能/职责”;但是,如果不继承自Component类,我们就”只能用一个装饰器对象去装饰具体构件对象,而且最多只能给具体构件对象添加一个功能/职责“),定义了一个与抽象构件类接口一致的接口,它保证每个具体装饰器都有一些必须具有的性质,比如,每个具体装饰器都有一个实例变量字段用以保存某个Component对象的引用。

  • ConcreteDecorator(具体装饰器类/角色)
    是一个具体的装饰器,继承自Decorator,实现具体要向被装饰对象(具体构件对象)添加的功能。用来装饰具体的构件对象或者另外一个具体的装饰器对象。


四、UML类图

1



五、情景例子的实现代码

将上面的情境用代码实现一番:

//礼物类(抽象构件类/角色)
public abstract class Present
{
    protected String description = "";

    public String getDescription()
    {
        return description;
    }

    public abstract double getPrice();

}


//围巾类(具体构件类)
public class Scarf extends Present
{
    public Scarf()
    {
        description = "一条围巾, 按顺序";
    }

    @Override
    public double getPrice()
    {
        return 20;
    }
}

//面霜类(具体构件类)
public class Cream extends Present
{
    public Cream()
    {
        description = "一瓶面霜, 按顺序";
    }

    @Override
    public double getPrice()
    {
        return 35;
    }
}

//包装纸类(装饰器类)
public abstract class Wrapper extends Present
{
    public abstract String getDescription();
}

//包装纸A类(具体装饰器类)
public class WrapperA extends Present
{
    protected Present present;

    public WrapperA(Present present)
    {
        this.present = present;
    }

    @Override
    public String getDescription()
    {
        return present.getDescription() + " 用包装纸A包装";
    }

    @Override
    public double getPrice()
    {
        return present.getPrice() + 2;
    }
}


//包装纸B类(具体装饰器类)
public class WrapperB extends Present
{
    protected Present present;

    public WrapperB(Present present)
    {
        this.present = present;
    }

    @Override
    public String getDescription()
    {
        return present.getDescription() + " 用包装纸B包装";
    }

    @Override
    public double getPrice()
    {
        return present.getPrice() + 1;
    }
}


//包装纸C类(具体装饰器类)
public class WrapperC extends Present
{
    protected Present present;

    public WrapperC(Present present)
    {
        this.present = present;
    }

    @Override
    public String getDescription()
    {
        return present.getDescription() + " 用包装纸C包装";
    }

    @Override
    public double getPrice()
    {
        return present.getPrice() + 3;
    }
}

//客户端代码
public class Client
{
    public static void main(String[] args)
    {
        //打算送围巾
        Present present = new Scarf();

        //先用包装纸B包装
        present = new WrapperB(present);

        //再用包装纸A包装
        present = new WrapperA(present);

        //最后用包装纸C包装
        present = new WrapperC(present);

        System.out.println("礼物是:" + present.getDescription());
        System.out.println("总共花费零用钱:" + present.getPrice());

    }
}

输出结果:

礼物是:一条围巾, 按顺序 用包装纸B包装 用包装纸A包装 用包装纸C包装
总共花费零用钱:26.0

或者:(客户端代码)

public class Client
{
    public static void main(String[] args)
    {
        //不断嵌套,是不是和Java中IO流的API的使用很相似?
        Present present = new WrapperC(new WrapperA(new WrapperB(new Scarf())));

        System.out.println("礼物是:" + present.getDescription());
        System.out.println("总共花费零用钱:" + present.getPrice());

    }
}



六、优点

  1. 该模式动态地将职责附加到对象上,就拓展功能而言,装饰者相比继承方式更加富有弹性、灵活性。因为在继承方式中,我们需要提前知道要拓展的功能具体是什么,而这是在编译期就确定了的,是静态的;但是,装饰者模式使得我们可以在运行期动态地根据需要去拓展现有对象的功能。
  2. 具体装饰器类与具体构件类可以独立发展,不会相互耦合。
  3. 该模式是继承方式的一个替代模式,可以动态扩展一个具体实现类的功能。
  4. 将类中的装饰功能的代码从类中移除,简化了原有的类。
  5. 有效地把类的核心职责和装饰功能分离开,并去除了相关类中重复的装饰逻辑。



七、缺点

  1. 会产生很多小类。使用该模式,类的数量会增加很多,系统复杂性增加,不能过度使用。最典型的例子就是Java中IO流的API对程序员造成的困扰。
  2. 多层装饰增加了复杂性。该模式比继承方式更加灵活,也意味着比继承方式更加易于出错,增加了排错的困难。对于多层装饰的对象,调试时寻找错误可能需要逐级排查,需要细心再细心,过程较为烦琐。



八、什么时候用

  1. 想在不影响其他对象的前提下,以动态、透明的方式给对象添加职责或功能。
  2. 若不适合用子类对现有对象进行功能拓展,可以考虑用装饰者模式。



九、其他想说的

  • Java中的IO其实就是装饰者模式的典型应用,我们经常使用的IO流的”套接”其实就是装饰者模式中不断对具体对象动态拓展功能/职责的体现。
  • 装饰器可以在被装饰者的职责/功能上扩展职责/功能,也可以完全替代被装饰者的职责/功能。
  • 对于装饰模式,装饰的顺序很重要!最理想的情况是,保证各个具体装饰器类彼此之间相互独立,这样它们就可以以任意顺序进行组合了。
  • 若只有一个ConcreteComponent类而没有抽象的Component类,则Decorator类可以是ConcreteComponent类的一个子类。若只有一个ConcreteDecorator类,则没有必要再建立一个Decorator类,我们可以把Decorator类和ConcreteDecorator类合并成一个类。

猜你喜欢

转载自blog.csdn.net/WuchangI/article/details/81289659