为什么说装饰器模式是运行时扩充,而继承是编译时扩充

一、装饰模式简介

装饰模式(Decorator)又名装饰者模式模式。

什么是装饰模式?装饰模式有哪些 特点?

1、动态的将责任附加到对象上,若要扩展功能,装饰者提供比继承更具弹性的替代方案。

2、装饰模式以对客户透明的方式动态的给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。

3、装饰模式可以在不创造更多子类的情况下、将对象的功能加以扩展。

4、装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展完全是透明的。

5、装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实 对象。

6、装饰对象和真实对象有相同的接口,装饰对象包含一个真实对象的引用,负责将请求转发给真实的对象。

7、装饰对象可以在转发这些请求前后增加一些附加自定义功能。

二、装饰模式与继承的比较

三、装饰模式和继承有啥不同 
1、装饰模式:
   (1)、用来扩展特定对象的功能。
   (2)、不需要子类。
   (3)、动态,运行时分配职责。灵活。
   (4)、一个给定的对象,同时可能有不同的装饰对象。
2、继承
    (1)、用来扩展一类对象的功能
    (2)、不需要子类
    (3)、静态
    (4)、编译的时候分配职责,缺乏灵活。
    (5)、导致很多子类产生,混杂。

实例1:

装饰器模式主要是为了扩充一个类的功能,也就是说,它把一个类进行了一定的装饰,使它有了新的功能,但保证了类原有的功能。实现的方法通常是把原有类作为装饰类构造函数的一个参数进行传入。

咋一看,好像装饰器模式和继承没什么区别,都是保证原有的功能,然后在扩充功能。但其实区别还是挺大的,装饰器模式最主要的优势是比较灵活,因为它修饰哪个类是在运行时才确定的;而继承中,继承哪个类是在编写哪个继承类的时候就要确定下来的,也就是说继承是编译时确认的。什么,有点晕?且看下面的例子:

复制代码

//Component 英雄接口
public interface Hero {
    //学习技能
    void learnSkills();
}
//ConcreteComponent 具体英雄盲僧,,注意,可以有多个实现,虽然我这里只实现了一个
public class BlindMonk implements Hero {

    private String name;

    public BlindMonk(String name) {
        this.name = name;
    }

    @Override
    public void learnSkills() {
        System.out.println(name + "学习了以上技能!");
    }
}
//Decorator 技能栏
public class Skills implements Hero{

    //持有一个英雄对象接口
    private Hero hero;

    public Skills(Hero hero) {
        this.hero = hero;
    }

    @Override
    public void learnSkills() {
        if(hero != null)
            hero.learnSkills();
    }
}
//ConreteDecorator 技能:Q
public class Skill_Q extends Skills{

    private String skillName;

    public Skill_Q(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能Q:" +skillName);
        super.learnSkills();
    }
}
//ConreteDecorator 技能:W
public class Skill_W extends Skills{

    private String skillName;

    public Skill_W(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能W:" + skillName);
        super.learnSkills();
    }
}
//ConreteDecorator 技能:E
public class Skill_E extends Skills{

    private String skillName;

    public Skill_E(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能E:"+skillName);
        super.learnSkills();
    }
}
//ConreteDecorator 技能:R
public class Skill_R extends Skills{

    private String skillName;

    public Skill_R(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能R:" +skillName );
        super.learnSkills();
    }
}
//客户端:召唤师
public class Player {
    public static void main(String[] args) {
        //选择英雄
        Hero hero = new BlindMonk("李青");

        Skills skills = new Skills(hero);
        Skills r = new Skill_R(skills,"猛龙摆尾");
        Skills e = new Skill_E(r,"天雷破/摧筋断骨");
        Skills w = new Skill_W(e,"金钟罩/铁布衫");
        Skills q = new Skill_Q(w,"天音波/回音击");
        //学习技能
        q.learnSkills();
    }
}

复制代码

    在上面的例子中,如果我要扩充hero的技能,而且我们不管这个hero到底是BlinMonk还是别的什么英雄,我们都想扩充他的技能。如果此时采用继承,那么我们必须为每一个英雄都派生一个子类,比如我要扩充盲僧的技能,那我就要从盲僧那里扩充一个类,要扩充提莫的技能,我又的从提莫那里扩充一个类(上面没有写提莫,如果要写的话,照着盲僧差不多写一个就行了),非常的不灵活。但是如果我们使用装饰模式,则只需要从他们的顶层类hero派生一个装饰类,并且这个装饰类的构造函数接受一个hero类型的对象作为传入参数。因为这个传入参数可以是任何hero的子类,所以他可以传入任何的英雄,无论是盲僧、刀妹还是提莫,并且因为这些子类都能够实现多态,因此实现了对所传入的子类对象的装饰。传入什么东西,不是由装饰器的编写者决定的,而是由装饰器的使用者决定的,所以对于库类的编写者来说,装饰器装饰什么东西(即传入什么参数)是它的代码已经交付之后发生的事了,所以我们说这是运行时决定的(动态库类代码可能确实已经在系统运行了)。

     同时,为了扩充装饰类本身,实现多样化装饰,我们可以先派生一个顶层的抽象装饰类,再从这个抽象的装饰类派生一些具体的装饰类。注意,这些派生出来的具体的装饰类具体增加什么新功能,和他们要装饰哪个类是无关的,因为他们要求能够装饰所有的hero而不是某一个hero,而是和他们要增加什么具体功能有关,比如增加R技能还是Q技能。

    另外,上面的例子还展示了装饰器类一个强大的功能,就是装饰器对象在构造的时候,可以传入另一个装饰器对象(因为它的顶层父类就是一个hero),从而能够使用另一个装饰类的方法,非常强大。但这里隐含了一个需要注意的地方,那就是装饰器必须实现被装饰类(此处为hero)的所有方法,并在这些方法中一般还要求调用被装饰类的对应方法,一次实现对被修饰类对象数据的操作(对象的数据一般都只能通过它自己的方法接口进行操作)

    因此可以看出,装饰器模式其实是充分地利用了继承多态的优势,让“扩充哪个类”这个决定也交给了运行时多态去确认,可谓灵活至极啊。

实例2:

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

比如要出门,给自己穿上各种衣服,就可以使用一个服装类:

#ifndef PERSON
#define PERSON
#include <QString>
#include <QTextStream>
#include <QtDebug>

class Person
{
    QString name;
public:
    Person(){}   //子类不想用构造函数,父类必须实现无参数构造函数(如果父类没有构造函数时,默认编译器产生的构造函数即可!)
    Person(QString n)
    {
        name = n;
    }
    virtual void show()
    {
        qDebug() <<"装扮的"<<name<<":";
    }
    virtual ~Person(){}
};

class Finery:public Person
{
protected:
    Person *component = nullptr;    //内含一个用来装饰的类,使用方式见main函数!
public:
    //装饰
    void Decorate(Person *com)
    {
        component = com;
    }
    void show() override   //把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此当需要执行特殊行为时,客户代码
                           //就可以在运行时根据需要有选择的、按顺序的使用装饰功能包装对象!!
    {
        if(component != nullptr)
            component->show();
    }
};

class Tshirt final:public Finery
{
public:
    void show() override
    {
        Finery::show();
        qDebug() <<"大T恤";
    }
};

class Skirt final:public Finery
{
public:
    void show() override
    {
        Finery::show();
        qDebug() <<"裙子";
    }
};

class stocking final:public Finery
{
public:
    void show() override
    {
        Finery::show();
        qDebug() <<"长袜";
    }
};

#endif // PERSON

然后在主函数中,一个人就可以随意的“穿上”各种衣服!

#include "person.h"

int main(int argc, char *argv[])
{
    Person *a = new Person("小红");
    Finery *t = new Tshirt();
    Finery *s = new Skirt();
    Finery *st = new stocking();
    t->Decorate(a);
    s->Decorate(t);
    st->Decorate(s);
    st->show();
    return 0;
}

这样就实现了简单的装饰模式。
最后,放上源码地址:https://github.com/Dongzhixiao/designMode_qt/tree/master/wearClothes_Decorator_Pattern_6

参考链接:

https://blog.csdn.net/Z0157/article/details/83514505

https://blog.csdn.net/qq_19528953/article/details/52516436

https://www.cnblogs.com/JMLiu/p/8081074.html

   

猜你喜欢

转载自blog.csdn.net/jiesunliu3215/article/details/108862689
今日推荐