本篇文章记录装饰器模式。

问题提出

以UI设计界面为例:
在这里插入图片描述
上图中,第一幅图是一个普通的列表控件,第二幅图增加了边框,第三图增加了垂直滚动条,第四幅图增加了水平滚动条。
ListCtrl表示第一幅图的普通控件类,提供draw方法;
BorderListCtrl类,继承自ListCtrl,增加了边框的列表控件,提供draw方法;
VerScBorderListCtrl类继承自BorderListCtrl,表示增加了边框又增加了垂直滚动条的列表控件,提供draw方法;
HorScVerScBorderListCtrl类,继承自VerScBorderListCtrl,表示增加了边框,垂直、水平滚动条的列表控件,提供draw方法;
如果用类的继承方式实现如下:

    class ListControl
    {
    public:
        virtual void draw()
        {
            cout << "实现了一个基本的ListControl控件类 " << endl;
            drawListControl();
        }
        void drawListControl()
        {
            cout << "实现一个普通边框功能" << endl;
        }
    };
    class ListControlBorder : public ListControl
    {
    public:
        virtual void draw() override
        {
            cout << "实现了一个带边框的ListControl控件类 " << endl;
            drawListControl();
            drawListControlBorder();
        }
        void drawListControlBorder()
        {
            cout << "实现了一个边框" << endl;
        }
    };
    class ListControlScrollVertical : public ListControlBorder
    {
    public:
        virtual void draw() override
        {
            cout << "实现了一个带边框的,有垂直滚动条的ListControl控件类 " << endl;
            drawListControl();
            drawListControlBorder();
            drawListControlScrollVertical();
        }
        void drawListControlScrollVertical()
        {
            cout << "增加了一个垂直滚动条" << endl;
        }
    };

    class ListControlHorizontalScroll : public ListControlScrollVertical
    {
    public:
        virtual void draw() override
        {
            cout << "实现了一个带边框的,有垂直和水平滚动条的ListControl控件类 " << endl;
            drawListControl();
            drawListControlBorder();
            drawListControlScrollVertical();
            drawHorizontalScroll();
        }

        void drawHorizontalScroll()
        {
            cout << "实现了一个水平滚动条" << endl;
        }
    };

    void test()
    {
        ListControlHorizontalScroll l1;
        l1.draw();
        /*
            实现了一个带边框的,有垂直和水平滚动条的ListControl控件类
            实现一个普通边框功能
            实现了一个边框
            增加了一个垂直滚动条
            实现了一个水平滚动条
        */
    }

上面通过逐层继承的方式实现了这四个图,但是有如下两个问题:
问题1:如果给一个普通列表增加新内容,例如阴影效果,外边框等,需要不断增加各种子类;
问题2:如果希望创建一个没有边框,但有垂直滚动条的列表控件,依然还要继承创建更多子类。
上边两个问题导致了子类数量泛滥,通过子类继承父类的方式解决该类问题不是一个好方法。
现在换一种解决方法:首先创建一个ListControl类代表普通列表控件,提供draw方法;然后向这个列表中增加装饰,如果增加一个边框,就形成一个带边框的控件;如果提供一个垂直滚动条,就形成一个带普通滚动条的控件等,这就是装饰模式的思想。
在这里插入图片描述

组合复用原则—原则6

首先引入组合复用原则:组合复用原则称为复合复用原则,聚合复用原则,是面向对象程序设计的一个重要原则。
组合复用原则核心思想:若实现继承方式的两个类,则父类代码修改可能会影响子类的行为,而且,父类的很多方法子类是用不上的,这是一种浪费。而如果使用组合方式设计这两个类,大大减少这两个类之间的依赖关系,也不会存在因继承关系导致的浪费。所以,如果继承和组合都可以达到设计的目的,优先考虑使用组合,组合优于继承,这就是组合复用原则核心思想。

装饰器模式

下面的代码实现了一个装饰器模式的控件类,具体实现如下:
1 一个控件类的抽象类,在类中定义了一个接口,用于向屏幕上绘制图。未来实现的其他控件,比如表格控件,列表控件都继承该类。基于控件抽象类实现一个列表类,继承自控件类。
2 然后实现一个抽象的装饰器类Decorator,父类依然是Control类;这相当于定义了一个装饰器抽象类完整的接口,在Decorator类中调用draw方法,绘制Control指向的具体控件;
3 实现具体的装饰器类,BorderDecorator类,VerticalScrollDecorator HorizontalScrollDecorator类,它们都继承自Decorator类,并重新实现draw方法,在实现过程中,调用父类的draw,父类的draw对应了绘制了要装饰的对象。
下面的代码实现一个基于控件的装饰器模式:

// 抽象的控件类, 所有控件继承该类
class Control
{
public:
	virtual void draw() = 0;  // 用于将自身绘制在屏幕上;
public:
	virtual ~Control() {}
};

// 列表控件类,继承自抽象的控件类
class ListCtrl : public Control
{
public:
	virtual void draw() // 
	{
		cout << "向屏幕上绘制一个普通控件类";
	}
};

// 抽象的装饰器类:用来修饰控件类
class Decorator : public Control
{
public:
	Decorator(Control* pControl) : m_pControl(pControl) {}
	virtual void draw()
	{
		m_pControl->draw();	 // 虚函数,调用哪个draw函数,取决于指针的实际类型
	}
private:
	Control* m_pControl;	// 需要被装饰的控件
};

// 边框装饰器类
class BorderDecorator : public Decorator
{
public:
	BorderDecorator(Control *tmpctrl) : Decorator(tmpctrl) 
	{
		
	}
	virtual void draw() override
	{
		Decorator::draw();	// 调用父类的draw函数,绘制Control类指向的类;
		drawBorder();		// 然后再绘制一个边框
	}
private:
	void drawBorder()
	{
		cout << "添加一个边框" << endl;
	}
};

// 具体的 垂直滚动条 装饰器类
class VerticalScrollDecorator : public Decorator
{
public:
	VerticalScrollDecorator(Control* tmpctrl) : Decorator(tmpctrl)
	{

	}
	virtual void draw() override
	{
		Decorator::draw();	// 调用父类的draw函数,绘制Control类指向的派生类控件
		drawVerticalScroll();		// 然后在当前控件样式的基础上 再绘制一个滚动条
	}
private:
	void drawVerticalScroll()
	{
		cout << "添加一个垂直滚动条" << endl;
	}
};

// 绘制水平滚动条的装饰器类
class HorizontalScrollDecorator : public Decorator
{
public:
	HorizontalScrollDecorator(Control* tmpctrl) : Decorator(tmpctrl)
	{

	}
	virtual void draw() override
	{
	Decorator::draw();	// 调用父类的draw函数,绘制Control类指向的派生类控件
	drawHorizontalScroll();	// 然后在当前控件样式的基础上 再绘制一个水平 滚动条
	}
private:
	void drawHorizontalScroll()
	{
		cout << "添加一个水平滚动条" << endl;
	}
};

// 绘制边框 + 水平滚动条的装饰器类
void test()
{
	// 先绘制一个普通列表控件
	Control* pCtrl = new ListCtrl();  
	
	// 绘制一个带边框的列表控件
	Decorator *pDecorator = new BorderDecorator(pCtrl);
	pDecorator->draw(); 
	// 绘制一个带边框,和 垂直滚动条的列表控件
	Decorator *pDecorator2 = new VerticalScrollDecorator(pDecorator);
	pDecorator2->draw();	// 
	/*
        向屏幕上绘制一个普通控件类
        添加一个边框
        添加一个垂直滚动条
    */
}
// 绘制一个水平滚动条的列表控件
void test2()
{
	Control *pCtrl = new ListCtrl();	// 创建一个列表控件	:本体
	Decorator *pDecorator = new HorizontalScrollDecorator(pCtrl); // 创建一个水平滚动条 再将列表控件作为参数传入,装饰
	pDecorator->draw();
}

装饰器模式的UML图

UML图如下:
在这里插入图片描述
上图包含了四种角色:
1 Control 抽象控件,它是ListControl和Decorator的父类;
2 ListControl 具体控件,定义具体的构件,实现抽象类中的接口。
3 Decorator抽象装饰器类,抽象类Control的子类,在定义了一个与Control接口一致的接口,子类通过对该接口的扩展,达到装饰的目的。
4 VerticalScrollDecorator等具体装饰器类:是抽象装饰器类的子类,每个具体装饰器类都增加了新方法来修饰构件。

另一个装饰器模式示例

装饰器类另一个范例:
1 一杯单纯的水果饮料,售价10元;
2 如果向饮料中添加一份砂糖,售价加2元;
3 如果向饮料中添加一份牛奶,售价加3元;
4 如果向饮料中添加一份珍珠,售价加4元;
5 求:加砂糖 + 珍珠的饮料的售价

// 抽象的饮料类,定义了一个获取价格的接口
class Beverage
{
public:
	virtual int getprice() = 0;
public:
	virtual ~Beverage() {}
};
// 水果饮料类
class FruitBeverage : public Beverage
{
public:
	virtual int getprice()
	{
		return 10;
	}
};
// 装饰器类
class Decorator : public Beverage
{
public:
	Decorator(Beverage* tmpbvg) : m_pBeverage(tmpbvg) {}  // 传入一个饮料类的指针
	virtual int getprice()
	{
		return m_pBeverage->getprice();	// 调用饮料类的getprice方法,获取饮料的价格
	}
private:
	Beverage* m_pBeverage;
};

// 砂糖装饰器类
class SugarDecorator : public Decorator
{
public:
	SugarDecorator(Beverage* tmpbvg) : Decorator(tmpbvg) {}
	virtual int getprice()
	{
		return Decorator::getprice() + 2;	// 调用父类的getprice方法,获取饮料的价格,然后再加2元
	}
};

// 牛奶装饰器类
class MilkDecorator : public Decorator
{
public:
	MilkDecorator(Beverage* tmpbvg) : Decorator(tmpbvg) {}
	virtual int getprice()
	{
		return Decorator::getprice() + 3;	// 调用父类的getprice方法,获取饮料的价格,然后再加3元
	}
};
// 珍珠装饰器类
class PearlDecorator : public Decorator
{
public:
	PearlDecorator(Beverage* tmpbvg) : Decorator(tmpbvg) {}
	virtual int getprice()
	{
		return Decorator::getprice() + 4;	// 调用父类的getprice方法,获取饮料的价格,然后再加4元
	}

};
void test()
{
	Beverage *pBeverage = new FruitBeverage();	// 创建一个水果饮料
	Decorator *pDecorator = new SugarDecorator(pBeverage);	// 创建一个砂糖装饰器,将水果饮料作为参数传入
	pDecorator->getprice();	// 获取砂糖饮料的价格
	Decorator*pDecorator2 = new PearlDecorator(pDecorator);	// 创建一个珍珠装饰器,将砂糖饮料作为参数传入
	int p = pDecorator2->getprice();	// 获取砂糖 + 珍珠饮料的价格 10 + 2 + 4 = 16
}

UML图如下:
在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐