大话设计模式解读03-装饰模式

发布于:2024-06-18 ⋅ 阅读:(22) ⋅ 点赞:(0)

本篇文章,来解读《大话设计模式》的第6章——装饰模式。并通过C++代码实现实例代码的功能。

注:第3~6章讲的是设计模式中的一些原则(第3章:单一职责原则;第4章:开放-封闭原则;第5章:依赖倒转原则和里氏替换原则),这些原则在设计模式中用到时会提及,暂不做专门解读。

1 装饰器模式

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

我们在给对象增加功能时,一种做法是再设计一个子类来继续父类,然后给子类添加额外的功能,另一种做法就是通过装饰模式,设计单独用于装饰功能的类,通过“包装”的形式动态给某个对象增加功能。

2 穿搭衣服实例

题目:用控制台程序,写可以给人搭配衣服的代码

2.1 版本一

版本一的代码,仅定义了一个Person类,提供6种不同服饰的装扮接口,以及1个名字展示接口。

2.1.1 Person类

Person类的代码如下,维护一个人的名字,然后是6种服饰的穿衣接口,就是加一句打印,最后Show接口显示人的名字。

class Person
{
public:
    Person(std::string name)
    {
        m_name = name;
    }
    
    void WearTShirts()
    {
        printf("大T恤 ");
    }
    
    void WearBigTrouser()
    {
        printf("垮裤 ");
    }
    
    void WearSneakrs()
    {
        printf("破球鞋 ");
    }
    
    void WearSuit()
    {
        printf("西装 ");
    }
    
    void WearTie()
    {
        printf("领带 ");
    }
    
    void WearLeatherShoes()
    {
        printf("皮鞋 ");
    }
    
    void Show()
    {
        printf("装扮的%s", m_name.c_str());
    }  
             
private:
    std::string m_name;
};

2.1.2 主函数

主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,然后依次调用穿衣接口,最后调用展示接口:

#include <iostream>

int main()
{
    Person xc = Person("小菜");
    
    printf("\n第一种装扮:");
    xc.WearTShirts();
    xc.WearBigTrouser();
    xc.WearSneakrs();
    xc.Show();
    
    printf("\n第二种装扮:");
    xc.WearSuit();
    xc.WearTie();
    xc.WearLeatherShoes();
    xc.Show(); 
    
    printf("\n");  
    
    return 0;
}

代码运行效果如下:

版本一这种方式,虽然功能实现了,但如果想要增加装扮,就需要修改Person类了,这违反了面向对象设计中的开放-封闭原则。

开放-封闭原则:是指软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。

换句话说:

  • 开放:对扩展开放,当需要增加新功能时,通过代码扩展的方式(如增加新的类)实现
  • 封闭:对修改封闭,当需要增加新功能时,尽量避免对原有代码的修改

下面来看版本二如何实现。

2.2 版本二

版本二是将各种服饰单独封装了起来,并继承自服饰抽象类,将服饰类与人类进行了分离,类图如下:

这样,后续需要增加服饰时,只需要增加对应的具体服饰类,而不会影响其它已有的服饰类的代码。

2.2.1 Person类与服饰类

Person类与服饰类的代码如下,Person类只维护一个人的名字,并通过Show接口显示人的名字。服饰类的Show接口用于显示服饰的名称,具体显示的内容由具体服饰类的Show接口实现,也是打印出服饰的名字。

// Person类
class Person
{
public:
    Person(std::string name)
    {
        m_name = name;
    }
    
    void Show()
    {
        printf("装扮的%s", m_name.c_str());
    }    
      
private:
    std::string m_name;
};

// 服饰类
class Finery
{
public:
    virtual void Show(){};
};

// 各种服饰子类
class TShirts : public Finery
{
public:
    void Show()
    {
        printf("大T恤 ");
    }    
};

class BigTrouser : public Finery
{
public:
    void Show()
    {
        printf("垮裤 ");
    }    
};

class Sneakrs : public Finery
{
public:
    void Show()
    {
        printf("破球鞋 ");
    }    
};

class Suit : public Finery
{
public:
    void Show()
    {
        printf("西装 ");
    }    
};

class Tie : public Finery
{
public:
    void Show()
    {
        printf("领带 ");
    }    
};

class LeatherShoes : public Finery
{
public:
    void Show()
    {
        printf("皮鞋 ");
    }    
};

2.2.2 主函数

主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,

然后依次实例化具体要装扮的服饰类并调用对应的展示接口,

最后调用Person的展示接口:

int main()
{
    Person xc = Person("小菜"); //先实例化一个名为"小菜"的Person对象
    
    printf("\n第一种装扮:");
    TShirts dtx; //依次实例化具体要装扮的服饰类
    BigTrouser kk;
    Sneakrs pqx;
    dtx.Show(); //调用对应的展示接口
    kk.Show();
    pqx.Show();
    xc.Show(); //最后调用Person的展示接口
    
    printf("\n第二种装扮:");
    Suit xz;
    Tie ld;
    LeatherShoes px;
    xz.Show();
    ld.Show();
    px.Show();
    xc.Show(); 
    
    printf("\n");  
    
    return 0;
}

代码运行效果如下:

版本二中,Rerson类和服饰类是完全独立的,搭配衣服的过程也只是一个一个将对应词打印出来。下面来看版本三。

2.3 版本三

版本三用到了本篇要讲的装饰器模式。

在本例中,服饰就是装饰类,具体的服饰,T恤、球鞋这些是具体的装饰类,而人就是要被装饰的组件,那是组件Component还是具体组件ConcreateComponet呢?

本例中,只有一个ConcreateComponet具体组件类,就没必要单独建立一个Component组件类,可以把两者职责合并成一个类,也就是只使用ConcreateComponet类表示Person类。

2.3.1 Person类与服饰类

Person类与服饰类的代码如下:

// Person类
class Person
{
public:
    Person(){};
    
    Person(std::string name)
    {
        m_name = name;
    }
    
    // 父类的函数
    virtual void Show()
    {
        printf("装扮的%s", m_name.c_str());
    }    
      
private:
    std::string m_name;
};

// 服饰类(Decorator)
class Finery : public Person
{
protected:
    Person *m_pComponent = nullptr;
    
public:
    void Decorate(Person *pComponent)
    {
        m_pComponent = pComponent;
    }
    
    // 子类的函数
    void Show()
    {
        if (m_pComponent != nullptr)
        {
            m_pComponent->Show();
        }
    }
};

// 具体服饰类(ConcreateDecorator)
class TShirts : public Finery
{
public:
    void Show()
    {
        printf("大T恤 ");
        Finery::Show();
    }   
};

class BigTrouser : public Finery
{
public:
    void Show()
    {
        printf("垮裤 ");
        Finery::Show();
    }    
};

class Sneakrs : public Finery
{
public:
    void Show()
    {
        printf("破球鞋 ");
        Finery::Show();
    }    
};

class Suit : public Finery
{
public:
    void Show()
    {
        printf("西装 ");
        Finery::Show();
    }    
};

class Tie : public Finery
{
public:
    void Show()
    {
        printf("领带 ");
        Finery::Show();
    }    
};

class LeatherShoes : public Finery
{
public:
    void Show()
    {
        printf("皮鞋 ");
        Finery::Show();
    }    
};

2.3.2 主函数

主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,

然后再依次实例化要装扮的服饰类对象,接着通过“包装”的方式,后一个对象对前一个对象进行包装,

最后调用最终包装后对象的展示接口:

int main()
{
    Person *xc = new Person("小菜");
    
    printf("\n第一种装扮:");
    TShirts    *dtx = new TShirts();
    BigTrouser *kk  = new BigTrouser(); 
    Sneakrs    *pqx = new Sneakrs();
    
    dtx->Decorate(xc); // 装饰过程:先用大裤衩装饰小菜
    kk->Decorate(dtx); // 再用垮裤装饰穿了大裤衩的小菜
    pqx->Decorate(kk); // 再用破球鞋装饰穿了大裤衩和垮裤的小菜
    pqx->Show();       // 最后调用最外层的装饰对象的展示接口

    printf("\n第二种装扮:");
    Suit         *xz = new Suit();
    Tie          *ld = new Tie();
    LeatherShoes *px = new LeatherShoes();
    
    xz->Decorate(xc); // 装饰过程:先用西装装饰小菜
    ld->Decorate(xz); // 再用领带装饰穿了西装的小菜
    px->Decorate(ld); // 再皮鞋装饰穿了西装和领带的小菜
    px->Show();       // 最后调用最外层的装饰对象的展示接口  

    printf("\n");  
    
    return 0;
}

代码运行效果如下:

装饰模式的优缺点与适用场景:

3 总结

本篇介绍了设计模式中的装饰模式,并通过给人装扮的实例,使用C++编程,来演示装饰模式的使用。