设计模式打怪升级之装饰器模式

开篇语


        学习设计模式有一段时间了,这也不是所学习的第一个设计模式,但是却是落笔总结的第一个模式,个中原因很明显,之前一直觉得该把学过的东西进行思考加以总结,算是一个归纳整理的过程,但是却只是停留在想法的层面,没有实际行动。俗话说,万事开头难,当你迈出第一步,才会有后面的千步、万步,直到理想的彼岸,但若是一直想想,却没勇气和恒心迈出第一步,那就只能永远在原地打转。

        废话不多说,直接进入正题。

装饰器模式


        个人感觉3w学习法,对于学习和思考设计模式来说,是非常适合的,在此将按照what(什么是XX设计模式)-> why(为什么要使用XX设计模式)-> How(怎么使用XX设计模式)这样的思路对设计模式进行学习。

What -- 什么是装饰器模式


维基百科给出了装饰器模式的基本原理:

装饰器模式:增加一个装饰类包裹原对象,包裹的方式一般是通过将原来的对象作为装饰类的构造函数的参数,并且装饰类同所修饰对象都必须继承自同一个基类,因此,装饰类和所修饰对象具有相同的接口,但实现不同的功能。对于使用者,这些都是透明的。


Why -- 为什么要使用装饰器模式


在最初学习C++的时候,觉得继承和多态就是整个世界,而学习过设计模式之后,才发现,最初的自己实在是图样图森破- -!
继承固然是C++中的核心 + 重中之重,但并不是万金油,很多时候,在系统设计过程中,继承显得非常力不从心(当然,你也可以在这些情况下,使用继承,但是设计出来的系统,我只能呵呵了)。

举个例子 -- 选自《Head First 设计模式》
对于一个咖啡连锁店(就是说星巴克、Costa这样的地方)的订单管理系统,我们知道,咖啡有很多种,比如摩卡、拿铁、香草拿铁.....等等等等,而且不同用户可能会有不同的特定需求,比如我就喜欢拿铁加两份奶,若是采用将所有的咖啡的公共属性提取出来,构成一个基类。然后所有的咖啡都继承自基类,并分别根据自己的特征实现相应的属性和处理函数,并不是不可以,但是会存在三个主要的问题:
1、咖啡的种类很多,很容易造成“类爆炸”,如下图所示


2、对于咖啡店,顾客就是上帝,你必须能灵活的满足顾客的所有需求。若是碰到奇葩顾客,需要加4份奶、3份糖的拿铁,怎么办?有人会说,可以在系统设计之初就把这些可能的情况加进去。但是!!!谁能保证可以想到所有可能的情况。既然做不到,那怎么办?依据上面的继承的方法,我们只能修改现有代码,在使用过程中,每遇到一次系统中没有的情况,就修改代码,加入相应的处理过程。这样的系统,我想,没人愿意使用。这显然违背了软件设计中的 封装变化原则(在软件设计过程中,应尽可能的把系统中不变的和可能发生改变的部分分离开,将变化的部分封装成固定的接口呈现给不变的部分,使得即使可变化的部分内部处理发生变化,系统中不变的部分依然可以不加修改的正确运行)。

3、若是配料的价格变了怎么办?难道我要一一修改使用该配料的咖啡子类的代码吗?

由上面,我们可以看出,在软件设计过程中,继承并不是万金油般的存在,在很多应用场景下,继承不是一个理想的选择。这其实就引出了软件设计中的另一条原则:
多用组合,少用继承

而装饰器模式设计的目标,基本上是基于解决上述咖啡馆问题的,即如何在不修改现有代码的情况下,就可搭配新的行为。这样的设计有什么好处呢?
这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。

由装饰器模式,我们可以引出第三条软件设计原则:
类应该对扩展开放,对修改关闭
即在设计一个类的时候,应尽可能的保证,若是需要添加新功能时,可以以该类的扩展的形式添加进系统,而不应该是需要修改现有的类代码。这将大大提升系统的灵活性和稳定性,并降低系统维护成本。

通过使用装饰器模式,可以在运行时扩充一个类的功能。 

装饰者模式的关系类图如图:


对应到咖啡馆问题时,可以这样设计:



到此,应该了解了为什么要使用装饰器模式了。因为我们想构建一个,对扩展开放,对修改关闭,具有弹性,可以应对改变,在不修改现有代码的情况下,就可搭配新行为的系统。


How -- 怎么使用装饰器模式


有上面装饰器模式的实现类图,可以了解到,装饰器模式系统,大抵由五部分组成:1、所要装饰对象的抽象类;2、所要装饰对象的具体实现类(派生自1);3、装饰器抽象类;4、具体装饰器类(派生自3);5、一个抽象基类(装饰器和所要装饰的对象都继承自此类,以保证对外提供以相同的接口 -- 无论用装饰器与否,对外部程序来说是透明的)。

具体的使用方法见代码说明:

// 饮料抽象基类的定义,包含有所有咖啡共有的属性和方法,和上面的Beverage对应
class Beverage
{
public:
	std::string description;

	Beverage()
	{
		description = "Unknown Beverage";
	}

	virtual std::string getDescription()
	{
		return description;
	}

	virtual double cost() = 0;
};
// 深度烘焙咖啡,继承自Beverage基类
class DarkRoast : public Beverage
{
public:
	DarkRoast()
	{
		description = "Dark Roast Coffee";
	}

	virtual double cost();
};
<pre name="code" class="cpp">// 深度烘焙cost函数具体实现
double DarkRoast::cost()
{
	return 0.99;
}
 
 
// 低咖啡因,继承自Beverage基类
class Decaf : public Beverage
{
public:
	Decaf()
	{
		description = "Decaf Coffee";
	}

	virtual double cost();
};
<pre name="code" class="cpp">// 低咖啡因cost的具体实现
double Decaf::cost()
{
	return 1.05;
}
 
 
// 浓缩,继承自Beverage基类
class Espresso : public Beverage
{
public:
    Espresso()
	{
		description = "Espresso";
	}

	virtual double cost();
};
<pre name="code" class="cpp">// 浓缩咖啡cost函数的具体实现
double Espresso::cost()
{
	return 1.99;
}
 
 
// 综合,继承自Beverage基类
class HouseBlend : public Beverage
{
public:
	HouseBlend()
	{
		description = "House Blend Coffee";
	}

	virtual double cost();
};
<pre name="code" class="cpp">// 综合cost函数的具体实现
double HouseBlend::cost()
{
	return 0.89;
}
 
 
// 装饰器抽象基类,继承自Beverage,使得装饰器和被装饰的对象有相同的接口,达到对调用者透明
class CondimentDecorator : public Beverage
{
public:
	virtual std::string getDescription() = 0;
};
// 牛奶装饰器的具体定义
class Milk : public Beverage
{
public:
	Milk(Beverage *beverage)
	{
		m_beverage = beverage;
		description = "Milk";
	}

	virtual std::string getDescription();
	virtual double cost();

private:
	Beverage *m_beverage;
};
<pre name="code" class="cpp">// 牛奶装饰器的具体实现
std::string Milk::getDescription()
{
	return m_beverage->getDescription() + " " + description;
}

double Milk::cost()
{
	return 0.10 + m_beverage->cost();
}
 
 
// 摩卡装饰器的具体定义
class Mocha : public CondimentDecorator
{
public:
	Mocha(Beverage *beverage)
	{
		m_beverage = beverage;
		description = "Mocha";
	}

	virtual std::string getDescription();
	virtual double cost();

private:
	Beverage *m_beverage;
};
<pre name="code" class="cpp">// 摩卡装饰器的具体实现
std::string Mocha::getDescription()
{
	return m_beverage->getDescription() + " " + <span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;">description;</span>
}

double Mocha::cost()
{
	return 0.20 + m_beverage->cost();
}
 
 
// 豆浆装饰器的具体定义
class Soy : public Beverage
{
public:
	Soy(Beverage *beverage)
	{
		m_beverage = beverage;
		description = "Soy";
	}

	virtual std::string getDescription();
	virtual double cost();

private:
	Beverage *m_beverage;
};
// 豆浆装饰器的具体实现
std::string Soy::getDescription()
{
	return m_beverage->getDescription() + " " + description;
}

double Soy::cost()
{
	return 0.15 + m_beverage->cost();
}

可以看出来,在装饰器的方法中,会调用所装饰对象的相应的方法,这其实是实现装饰器模式的核心之所在。
以上,我们采用装饰器模式,实现了一个咖啡订单管理系统,可以看到,我们可以随意的将调料和咖啡进行组合,极其灵活。同时,若是引入新品种的咖啡或者调料,只要按照当前结构,实现相应的子类即可。并且,若是调料或者咖啡的价格发生变动,我们只需要修改相应的子类里的数据即可,非常便于扩展。
下面为测试代码:

#include <iostream>

#include "HouseBlend.h"
#include "DarkRoast.h"
#include "Decaf.h"
#include "Espresso.h"

#include "Mocha.h"
#include "Milk.h"
#include "Soy.h"

int main()
{
	Beverage *beverage = new Espresso();
	std::cout << beverage->getDescription() << std::endl;

	Beverage *beverage2 = new DarkRoast();
	beverage2 = new Mocha(beverage2);
	beverage2 = new Soy(beverage2);
	std::cout << beverage2->getDescription() << beverage2->cost() << std::endl;

	Beverage *beverage3 = new HouseBlend();
	beverage3 = new Milk(beverage3);
	beverage3 = new Mocha(beverage3);
	beverage3 = new Soy(beverage3);
	std::cout << beverage3->getDescription() << beverage3->cost() << std::endl;

	return 0;
}

总结

1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式;
2、在软件设计中,应该允许行为可以被扩展,而无须修改现有的代码;
3、组合和委托可用于在运行时动态地加上新的行为;
4、装饰器可以在被装饰者的行为前后与|或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的;
5、可以用无数个装饰器包装一个组件;
6、装饰器一般对组件的客户是透明的,除非客户程序依赖组件的具体类型(指依赖于具体的派生类类型,这时可能无法再使用基类类型的指针来对组件进行引用);
7、装饰器模式会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值