Bootstrap

装饰模式(Decorator)模式:装饰模式/包装模式,结构模式

目录

问题背景

基本概念

一个小案例引入

用继承来实现吗?

用组装代替继承,防止类数量过多

引入装饰器(Decorator)模式

案例一装饰模式UML图

需求一

用装饰器模式实现

用继承实现

需求二

用装饰器模式实现

用继承实现

需求三

用装饰器模式实现

用继承实现

另一个装饰器模式范例

案例二装饰模式UML图

小结


问题背景

基本概念

装饰(Decorator)模式 :装饰器模式/包装模式,结构模式。展现出来运行时可扩展能力,比继承更强大,更灵活的设计思想。

一个小案例引入

1. 第一幅图中,每一个小格子称为一个控件。

2. 第一幅图中,游戏主角包裹的UI界面,在每个格子会放一下道具,装备之类的东西。如第二幅图所示,也会增加一些活动通知规则。

3. 第一幅图中,如果一个界面中格子数量不够,会在右侧增加 垂直滚动条 ,这样用户可以往下滑增加格子的数量,用来存放更多的信息。

如上图所示,先后为一个普通的列表控件,增加边框增加垂直滚动条增加水平滚动条后的效果。并且顺序一定是:增加边框 -> 增加垂直滚动条 -> 增加水平滚动条。并且顺序不能乱。

用继承来实现吗?

我们知道使用继承是 实现新功能 和 代码复用的有效手段。子类继承父类,并在子类中 新增新的成员函数,就可以实现新功能。

如果使用继承应该怎么样实现上述功能?

a) ListCtrl类代表普通列表控件,提供draw方法
b) BorderListCtrl类,继承自ListCtrl,左边增加了边框的列表控件,提供draw方法
c) VerScBorderListCtrl类继承自BorderListCtrl,表示增加了边框又增加了垂直滚动条的列表控件,提供draw方法
d) HorScVerScBorderListCtrl类,继承自 VerScBorderListCtrl,表示增加了边框,垂直、水平滚动条的列表控件,提供draw方法

用继承实现的缺点

1. 随着需求增多,后续可能会增加更多的新需求,在控件中增加阴影,增加各种颜色,增加各种动态效果等等.....,需要不断地创建各种子类。

2. 如果只想创建 一个带水平滚动条的列表控件,不带边框,不带垂直滚动条的列表控件,那么只能创建一个新类出来。

3. 如果只想创建 一个带垂直滚动条的列表控件,不带边框,不带水平滚动条的列表控件,那么也只能创建一个新类出来。 

如果用继承实现的的话,会导致类的数量比较多,灵活性也比较差。

用组装代替继承,防止类数量过多

a) ListCtrl类代表普通列表控件,提供draw方法
b) 增加边框 -> 带边框的列表控件
c) 增加垂直滚动条 -> 带纯质滚动条的列表控件,再给这个带垂直滚动条的列表控件增加一个水平滚动条 -> 既带垂直滚动条又带水平滚动条的列表控件。

如上图所示,为列表控件增加不同的装饰可以得到不同的列表控件。

这种通过装饰方式将一个类的功能不断增加的思想(动态的增加新功能),就是装饰模式的核心设计思想。

引入装饰器(Decorator)模式

1. 组合复用原则(Composite Reuse Principle,CRP),也称为合成复用原则/聚合复用原则。

2. 若两个使用继承进行设计,则父类代码的修改可能影响子类的行为,而且,可能父类中的很多方法子类是用不上的,这显然是一种浪费, 若使用组合进行设计,则可以大大降低两个类之间的依赖关系,也不会存在因继承关系导致的浪费行为,所以如果继承和组合都能达到设计目的,优先考虑使用组合(组合优于继承)。

3.“装饰”设计模式的定义(实现意图):动态的给一个对象添加一些额外的职责。就增加功能来说,该模式相比生成子类更加灵活。

案例一装饰模式UML图

    //装饰模式包含的四种角色:
     //a:Control(抽象构件):draw,让调用者以一致的方式处理未被修饰的对象以及经过修饰之后的对象,实现客户端的透明操作。
     //b:ListCtrl(具体构件):实现抽象构件定义的接口,此后,装饰器就可以给该构件增加额外的方法(职责);
     //c:Decorator(抽象装饰器类):
     //d:BorderDec、VerScrollBarDec、HorScrollBarDesc(具体装饰器类):增加了一些新方法,然后通过对draw接口的扩展,达到最终的修饰目的。

需求一

用装饰器模式实现

(1) : 创建一个又带边框,又带垂直滚动条的列表控件

namespace _nmsp1
{
	//抽象的控件类
	class Control
	{
	public:
		virtual void draw() = 0; //draw方法,用于将自身绘制到屏幕上。
	public:
		virtual ~Control() {} //做父类时析构函数应该为虚函数
	};

	//列表控件类
	class ListCtrl :public Control
	{
	public:
		virtual void draw()
		{
			cout << "绘制普通的列表控件!" << endl; //具体可以用DirectX或OpenGL来绘制
		}
	};

	//抽象的装饰器类
	//Decorator类 和 Control类 不仅仅是继承关系,也是组合关系
	class Decorator :public Control
	{
	public:
		Decorator(Control* tmpctrl) :m_control(tmpctrl) {} //构造函数
		virtual void draw()
		{
			m_control->draw(); //虚函数,调用的是哪个draw,取决于m_control指向的对象
		}
	private:
		Control* m_control; //需要被装饰的其他控件,这里用的是Control *;
	};

	//----------------------
	//具体的“边框”装饰器类
	class BorderDec :public Decorator
	{
	public:
		BorderDec(Control* tmpctrl) :Decorator(tmpctrl) {} //构造函数
		virtual void draw()
		{
			Decorator::draw(); //调用父类的draw方法以保持以往已经绘制出的内容
			drawBorder(); //也要绘制自己的内容
		}
	private:
		void drawBorder()
		{
			cout << "绘制边框!" << endl;
		}
	};

	//具体的“垂直滚动条”装饰器类
	class VerScrollBarDec :public Decorator
	{
	public:
		VerScrollBarDec(Control* tmpctrl) :Decorator(tmpctrl) {} //构造函数
		virtual void draw()
		{
			Decorator::draw(); //调用父类的draw方法以保持以往已经绘制出的内容
			drawVerScrollBar(); //也要绘制自己的内容
		}
	private:
		void drawVerScrollBar()
		{
			cout << "绘制垂直滚动条!" << endl;
		}
	};

	//具体的“水平滚动条”装饰器类
	class HorScrollBarDec :public Decorator
	{
	public:
		HorScrollBarDec(Control* tmpctrl) :Decorator(tmpctrl) {} //构造函数
		virtual void draw()
		{
			Decorator::draw(); //调用父类的draw方法以保持以往已经绘制出的内容
			drawHorScrollBar(); //也要绘制自己的内容
		}
	private:
		void drawHorScrollBar()
		{
			cout << "绘制水平滚动条!" << endl;
		}
	};
}

int main(int argc, char* argv[])
{
	// (1) : 创建一个又带边框,又带垂直滚动条的列表控件
	//首先绘制普通的列表控件
	_nmsp1::Control* plistctrl = new _nmsp1::ListCtrl(); //本体

	//接着“借助普通的列表控件”,可以通过边框装饰器绘制出一个“带边框的列表控件”
	_nmsp1::Decorator* plistctrl_b = new _nmsp1::BorderDec(plistctrl); //_nmsp1::Decorator*用成_nmsp1::Control *

	//接着“借助带边框的列表控件”,就可以通过垂直滚动条装饰器绘制出一个“带垂直滚动条又带边框的列表控件”
	
	_nmsp1::Decorator* plistctrl_b_v = new _nmsp1::VerScrollBarDec(plistctrl_b);
	plistctrl_b_v->draw(); //这里完成最终绘制


   delete plistctrl;
   delete plistctrl_b;
   delete plistctrl_b_v;


   return 0;

}

用继承实现

//用继承来实现
namespace _nmsp0
{
	//抽象的控件类
	class Control
	{
	public:
		virtual void draw() = 0; //draw方法,用于将自身绘制到屏幕上。
	public:
		virtual ~Control() {} //做父类时析构函数应该为虚函数
	};

	//列表控件类
	class ListCtrl :public Control
	{
	public:
		virtual void draw()
		{
			cout << "绘制普通的列表控件!" << endl; //具体可以用DirectX或OpenGL来绘制
		}
	};

	//具体的“边框”类
	class BorderListCtrl : public ListCtrl
	{
	public:
		virtual void draw()
		{
			ListCtrl::draw();
			drawBorder();
		}
	private:
		void drawBorder()
		{
			cout << "绘制边框!" << endl;
		}
	};

	//具体的“垂直滚动条”类
	class VerScBorderListCtrl : public BorderListCtrl
	{
	public:
		virtual void draw()
		{
			BorderListCtrl::draw();
			drawVerScrollBar();
		}
	private:
		void drawVerScrollBar()
		{
			cout << "绘制垂直滚动条!" << endl;
		}
	};

	///具体的“水平滚动条”类
	class HorScrollBarDec : public VerScBorderListCtrl
	{
	public:
		virtual void draw()
		{
			VerScBorderListCtrl::draw();
			drawHorScrollBar();
		}
	private:
		void drawHorScrollBar()
		{
			cout << "绘制水平滚动条!" << endl;
		}
	};
}

int main(int argc, char* argv[])
{
    //用继承实现
	//(1) : 创建一个又带边框,又带垂直滚动条的列表控件
	VerScBorderListCtrl
	_nmsp0::VerScBorderListCtrl* p_VerScBorderListCtrl = new _nmsp0::VerScBorderListCtrl();
	p_VerScBorderListCtrl->draw();
    


    delete p_VerScBorderListCtrl;


    return 0;
}

需求二

(2)创建一个只带水平滚动条的列表控件。

用装饰器模式实现

//(2)创建一个只带水平滚动条的列表控件
//首先绘制普通的列表控件
_nmsp1::Control* plistctrl2 = new _nmsp1::ListCtrl(); //本体


//接着“借助普通的列表控件”,可以通过水平滚动条装饰器绘制出一个“带水平滚动条的列表控件”
_nmsp1::Decorator* plistctrl2_h = new _nmsp1::HorScrollBarDec(plistctrl2);
plistctrl2_h->draw();



delete plistctrl2;
delete plistctrl2_h;

用继承实现

//(2)创建一个只带水平滚动条的列表控件
//需求(2),必须重新定义新类实现,不够灵活。

需求三

(3)创建一个只带垂直滚动条的列表控件。

用装饰器模式实现

//(3) 创建一个只带垂直滚动条的列表控件
//首先绘制普通的列表控件
_nmsp1::Control* plistctrl3 = new _nmsp1::ListCtrl(); //本体
//接着“借助普通的列表控件”,可以通过水平滚动条装饰器绘制出一个“带水平滚动条的列表控件”
_nmsp1::Decorator* plistctrl3_h = new _nmsp1::VerScrollBarDec(plistctrl3);
plistctrl3_h->draw();


delete plistctrl3;
delete plistctrl3_h;

用继承实现

//(3)  创建一个只带垂直滚动条的列表控件
//需求(3),必须重新定义新类实现,不够灵活。

另一个装饰器模式范例

案例二装饰模式UML图

奶茶与水果饮料的装饰模式UML图。

    //(3)另一个装饰模式的范例:计算水果饮料最终价格
    //a)一杯单纯的水果饮料,售价为10元。
    //b)如果向饮料中增加砂糖,则额外要加多1元。
    //c)如果向饮料中增加牛奶,则额外要加多2元。
    //d)如果向饮料中增加珍珠,则额外要加多2元。
    //e)又加珍珠又加砂糖,10+2+1 = 13

以下例子以装饰 水果饮料 为例,如果要装饰奶茶,只需要把 水果饮料类 改为 奶茶类就行,代码也类似,就不重复写了。

namespace _nmsp2
{
	//抽象饮料类
	class Beverage
	{
	public:
		virtual int getprice() = 0; //获取价格
	public:
		virtual ~Beverage() {}
	};

	//水果饮料类
	class FruitBeverage : public Beverage
	{
	public:
		virtual int getprice()
		{
			return 10; //一杯单纯的水果饮料,售价为10元
		}
	};

	//抽象的装饰器类
	//Decorator 类 和 Beverage类不仅仅是继承关系,也是组合关系
	class Decorator :public Beverage
	{
	public:
		Decorator(Beverage* tmpbvg) :m_pbvg(tmpbvg) {} //构造函数
		virtual int getprice()
		{
			return m_pbvg->getprice();
		}
	private:
		Beverage* m_pbvg;
	};

	//具体的“砂糖”装饰器类
	class SugarDec :public Decorator
	{
	public:
		SugarDec(Beverage* tmpbvg) :Decorator(tmpbvg) {} //构造函数
		virtual int getprice()
		{
			return Decorator::getprice() + 1; //额外加多1元,要调用父类的getprice方法以把以往的价格增加进来
		}
	};

	//具体的“牛奶”装饰器类
	class MilkDesc :public Decorator
	{
	public:
		MilkDesc(Beverage* tmpbvg) :Decorator(tmpbvg) {} //构造函数
		virtual int getprice()
		{
			return Decorator::getprice() + 2; //额外加多2元,要调用父类的getprice方法以把以往的价格增加进来
		}
	};

	//具体的“珍珠”装饰器类
	class BubbleDesc :public Decorator
	{
	public:
		BubbleDesc(Beverage* tmpbvg) :Decorator(tmpbvg) {} //构造函数
		virtual int getprice()
		{
			return Decorator::getprice() + 2; //额外加多2元,要调用父类的getprice方法以把以往的价格增加进来
		}
	};
}

主函数如下:

int main(int argc, char* argv[])
{
    //创建一杯单纯的水果饮料,价格10元:
	_nmsp2::Beverage* pfruit = new _nmsp2::FruitBeverage();
	//向饮料中增加珍珠,价格多加了2元
	_nmsp2::Decorator* pfruit_addbubb = new _nmsp2::BubbleDesc(pfruit);
	//再向饮料中增加砂糖,价格又加多了1元
	_nmsp2::Decorator* pfruit_addbubb_addsugar = new _nmsp2::SugarDec(pfruit_addbubb);
	//输出最终的价格
	cout << "加了珍珠又加了砂糖的水果饮料最终价格是:" << pfruit_addbubb_addsugar->getprice() << "元人民币" << endl;


	delete pfruit_addbubb_addsugar;
	delete pfruit_addbubb;
	delete pfruit;
    

    return 0;
}

小结

优点:“装饰”设计模式的定义(实现意图):动态的给一个对象添加一些额外的职责。就增加功能来说,该模式相比生成子类更加灵活。

缺点:会产生很多小对象,这些小对象大部分是类似的,小对象多了,会占用资源,从而影响程序性能。

;