C++设计模式(10):装饰模式

一、背景

在面向对象的开发中,如果想给一个类或者对象增加一个行为,首先想到的是利用类的继承,通过继承一个现有类,可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机,如果能够用组合的做法扩展对象的行为,就可以在运行时动态的进行扩展。

二、模式定义

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

模式分析

  • 装饰模式可以在程序运行的某个时刻动态地增加或者删除某个职责,这个只是对某个对象添加,并不是对整个类
  • 装饰模式提供了一种“即插即用”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用装饰类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。
  • 与继承相比,关联的主要优势在于不会破坏类的封装性,且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联虽然不会比继承减少编码量,但在软件维护阶段,由于关联使系统具有较好的松耦合性,因此使得系统更加容易维护。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

三、模式角色和UML类图

Component(抽象构件)它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责。

Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。

ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

由于具体构件类装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计。
在这里插入图片描述
在这里插入图片描述
代码示例

/* 抽象构件类 */
class Component {
public:
	virtual void Operation() = 0;
};

/* 具体构件类 */
class ConcreteComponent :public Component {
public:
	void Operation() {
		cout << "ConcreteComponent." << endl;
	}
};

/* 抽象装饰类 */
class Decorator :public Component {
public:
	Decorator(Component *p) : p_Component(p) {}

	void Operation(){
		if (p_Component != NULL){
			p_Component->Operation();
		}
	}
private:
	/* 维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法 */
	Component *p_Component;
};

/* 具体装饰类A */
class DecoratorA :public Decorator {
public:
	DecoratorA(Component *p) : Decorator(p) {}

	void Operation() {
		add_bahaviorA();
		Decorator::Operation();
	}

	/* 相当于给构件增加的职责 */
	void add_bahaviorA(){
		cout << "Added a BahaviorA." << endl;
	}
};

/* 具体装饰类B */
class DecoratorB :public Decorator {
public:
	DecoratorB(Component *p) : Decorator(p) {}

	void Operation(){
		add_bahaviorB();
		Decorator::Operation();
	}

	/* 相当于给构件增加的职责 */
	void add_bahaviorB(){
		cout << "Added a  BahaviorB." << endl;
	}
};

/* 客户端 */
int main(){
	/* 1.创建一个待被装饰的具体构件 */
	Component* object = new ConcreteComponent ();

	/* 2.装饰具体构件 */
	object = new DecoratorA(object);
	object->Operation();
	cout << "-----------------------------------------------------" << endl;
	/* 3.装饰具体构件 */
	object = new DecoratorB(object);
	object->Operation();
	cout << "------------------------------------------------------" << endl;
	return 0;
}

实际应用:Java I/O中的装饰者模式
使用 Java I/O 的时候总是有各种输入流、输出流、字符流、字节流、过滤流、缓冲流等等各种各样的流,不熟悉里边的设计模式的话总会看得云里雾里的,现在通过设计模式的角度来看 Java I/O,会好理解很多。

先用一幅图来看看Java I/O到底是什么,下面的这幅图生动的刻画了Java I/O的作用。
在这里插入图片描述
由上图可知在Java中应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。
流的来源主要有三种:本地的文件(File)、控制台、通过socket实现的网络通信
下面的图可以看出Java中的装饰者类和被装饰者类以及它们之间的关系,这里只列出了InputStream中的关系:
在这里插入图片描述
由上图可以看出只要继承了FilterInputStream的类就是装饰者类,可以用于包装其他的流,装饰者类还可以对装饰者和类进行再包装。

四、模式总结

适用场景

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  3. 尽量保持具体构件类是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,我们可以通过装饰类对其进行扩展。

优点

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

缺点

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值