C++设计模式——装饰模式(Decorator Pattern)

C++设计模式——装饰模式(Decorator Pattern)

微信公众号:幼儿园的学霸

目录

定义

Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生产子类更为灵活

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

既然可以动态增加功能,那么这功能也可以动态地被撤销
重点:扩展/增强 对象的功能 ,而非 限制 对象的功能

装饰模式要解决的问题:提供一种修改类的行为,而避免创建众多的派生类的途径。它是一种对象结构型模式,就增加功能来说,装饰模式比生成子类更为灵活。

装饰模式的角色组成有:

  • 抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。
  • 装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。
  • 具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。
    其UML类图如下:
    装饰模式UML类图

从装模式的UML类图可以看到,装饰模式的具体使用流程如下:
1.装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互;
2.装饰对象包含一个真实对象的引用(reference);
3.装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象;
4.装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

上面的流程是不是和代理模式很像呢?

代码示例

下面代码展示了一层装饰和层层装饰的结果。

#include <bits/stdc++.h>

//
//装饰模式
//关键代码:
// 1.Component 类充当抽象角色,不应该具体实现
// 2.修饰类引用和继承 Component 类,具体扩展类重写父类方法。
//

using namespace std;

//抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
class Component {
public:
    virtual ~Component() = default;

    virtual void configuration() = 0;
};

//具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
class Car : public Component {
public:
    void configuration() override {
        std::cout << "A Car" << std::endl;
    }
};

//装饰(Decorator)角色:持有一个构件(Component)对象的实例,
// 并实现一个与抽象构件接口一致的接口。
class DecorateCar : public Component {
public:
    DecorateCar(std::shared_ptr<Component> car) : m_pCar(car) {}

    void configuration() override {
        m_pCar->configuration();
    }

private:
    std::shared_ptr<Component> m_pCar;
};

//具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。
class DecorateLED : public DecorateCar {
public:
    DecorateLED(std::shared_ptr<Component> car) : DecorateCar(car) {}

    void configuration() override {
        DecorateCar::configuration();
        //or add other somthing
        addLED();
    }

private:
    void addLED() {
        std::cout << "Install LED" << std::endl;
    }
};

//具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。
class DecoratePC : public DecorateCar {
public:
    DecoratePC(std::shared_ptr<Component> car) : DecorateCar(car) {}

    void configuration() override {
        DecorateCar::configuration();
        addPC();
    }

private:
    void addPC() {
        std::cout << "Install PC" << std::endl;
    }
};

//具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。
class DecorateEPB : public DecorateCar {
public:
    DecorateEPB(std::shared_ptr<Component> car) : DecorateCar(car) {}

    void configuration() override {
        DecorateCar::configuration();
        addEPB();
    }

private:
    void addEPB() {
        std::cout << "Install Electrical Park Brake" << std::endl;
    }
};

int main() {
    std::shared_ptr<Car> car = std::make_shared<Car>();
    std::shared_ptr<DecorateLED> ledCar = std::make_shared<DecorateLED>(car);//装饰一层
    ledCar->configuration();

    std::cout << std::string(30, '-') << std::endl;

    std::shared_ptr<DecoratePC> pcCar = std::make_shared<DecoratePC>(ledCar);//装饰二层
    std::shared_ptr<DecorateEPB> epbCar = std::make_shared<DecorateEPB>(pcCar);//装饰三层
    epbCar->configuration();

    return 0;
    //运行结果如下:
    //A Car
    //Install LED
    //------------------------------
    //A Car
    //Install LED
    //Install PC
    //Install Electrical Park Brake
}

我们在DecorateEPB::configuration()函数中加入断点,查看一下执行到这步时,m_pCar对象被那些"装饰"进行了装饰,如下图所示:
断点调试

层层嵌套,犹如套娃。这样的代码出现点问题,排查可有点难度。

总结

装饰模式和代理模式

模式区别除了实现外,最重要的是模式的目的性。代理模式与装饰器模式最根本区别是两者的目的不同,而不在具体的实现差异。

下图为代理模式的UML类图
代理模式类图
观察代理模式的UML类图和装配模式的UML类图,可能使我们产生困惑。这两个设计模式看起来很像。对装饰器模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个 接口。对代理模式来说,代理类(proxy class)和真实处理的类(real class)都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法

那区别呢?
装饰模式:以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,另外,可以看到装饰模式能够对对象层层装饰;
代理模式:给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用;

装饰模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,并不提供对象本身的增强功能

二者的实现机制确实是一样的,可以看到他们的实例代码重复是很多的。但就语义上说,这两者的功能是相反的,模式的一个重要作用是简化其他程序员对你程序的理解,

你在一个地方写装饰,大家就知道这是在增加功能,你写代理,大家就知道是在限制,

来自于一篇文章的一段评论,原文章没有解释清楚,但是评论倒是清晰明了:

代理更多的是强调控制对对象的访问,比如说:访问A对象的查询功能时,访问B对象的更新功能时,访问C对象的删除功能时都需要判断用户是否登录,那么我需要将判断用户是否登录功能抽出来,并对A对象,B对象,C对象进行代理使访问它们时都需要去判断用户是否登录,简单说就是将某个控制访问权限应用到多个对象上;而装饰更多的强调现在我要给A对象增加唱歌功能,跳舞功能,说唱功能等等,简单说就是将多个功能附加在一个对象上

之前为项目编写的一个视频读取类,将cv::VideoCapture作为成员变量,控制对对象的使用,额外添加了视频预处理、视频保存等功能,我一直认为是代理模式,但是仔细学习之后,才明确是装饰模式。

适用场景

当系统需要增加新功能时,如果向旧的类中添加新的代码,通常这些代码是装饰了原有类的核心职责或主要行为,它们在主类中加入了新的字段、新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的代码仅仅只是为了满足一些只在某种特定情况下才会执行的特殊行为的需求。这时装饰模式就是一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择、按顺序地使用装饰功能包装对象了。

同一个接口,一个类在另外一个类基础上实现增强方法,以前是采用继承,为什么要采用装饰者模式呢?
类继承一旦父类改变,那么子类也得相应进行改变,也就是说它们之间的耦合性太高,不利于代码的维护和管理,使用装饰者模式可以将装饰类和被装饰类彼此独立起来,只需要实现共同的接口,一旦需要改代码升级时,只需要更改相应的类就可以了,不需要都改变。

优缺点

  • 优点
    1.使用装饰模式扩展功能不会产生类爆炸。它采用的是合成方式,比继承方式更加灵活;
    2.通过使用不同的具体装饰者类及它们不同的组合顺序,可以得到不同装饰后具有不同行为或者状态的对象;
    3.符合开闭原则。
  • 缺点
    1.增加了抽象装饰者类和具体装饰者类,一定程度增加了系统的复杂度,加大了系统的学习和理解成本;
    2.灵活性也意味着更容易出错,对于多次被多次修饰的对象,调试时寻找错误可能需要找到多个地方。

装饰模式的简化

  • 只有一个具体装饰者,这样就不需要抽象装饰者,具体装饰者直接继承抽象构件就可以了
    装饰模式简化1

  • 只有一个具体构件,这样就不需要抽象构件,抽象装饰者可以直接继承具体构件就可以了
    装饰模式简化2

  • 就是上面组合起来:只有一个具体构件和一个具体装饰者,这样抽象角色都不需要了,具体装饰者直接继承集体构件就可以了
    装饰模式简化3

参考资料

1.设计模式系列之「装饰模式」
2.装饰者模式
3.Java 代理模式和装饰者模式的区别
4.装饰模式



下面的是我的公众号二维码图片,按需关注
图注:幼儿园的学霸

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值