修饰模式,又叫Decorator模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

定义

动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。 — 《设计模式》 GoF

Motivation 动机

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
  • 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)

装饰模式可以在不需要创建更多子类的情况下,将对象的功能加以扩展,通过多态机制可以在运行时选择装配某一个具体构件,而不需要在每一个具体构件下面创建子类,增强了代码的复用性,符合GoF给出的定义,消除重复代码和减少子类个数。同时也符合设计模式的开闭原则,即扩展对象的功能时只需要增加装饰类即可,无需修改代码。

模式分析

  • Component : 抽象构件
  • ConcreteComponent: 具体构件,可以有多个
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类,可以有多个

代码分析

** 书本类 **

书本类是一个抽象构件,包括书的名字,页数和价格,都是纯虚函数,因此这是一个虚基类。

class book
{
public:
	virtual ~book();

	virtual void name(char *p_name) = 0;
	virtual int page() = 0;
	virtual int price() = 0;
};

** 数学书和英语书 **

  • 这是两个具体的抽象构件,对比上面的模式结构图,即把ConcreteComponent扩展成两个。
class MathBook : public book
{
public:
	MathBook();
	~MathBook();

	virtual void name(char *p_name);
	virtual int page();
	virtual int price();
};
MathBook::MathBook()
{
}

MathBook::~MathBook()
{
}

void MathBook::name(char *p_name)
{
	memcpy(p_name, "math book\0", sizeof("math book\0"));
}

int MathBook::page()
{
	return 100;
}

int MathBook::price()
{
	return 20;
}

class EnglishBook : public book
{
public:
	EnglishBook();
	~EnglishBook();

	virtual void name(char *p_name);
	virtual int page();
	virtual int price();
};

EnglishBook::EnglishBook()
{
}

EnglishBook::~EnglishBook()
{
}

void EnglishBook::name (char *p_name)
{
	memcpy(p_name, "English book\0", sizeof("English book\0"));
}

int EnglishBook::page()
{
	return 200;
}

int EnglishBook::price()
{
	return 80;
}

** 抽象装饰类 **

装饰类继承book类,这里面没有方法,只有一个book类的指针对象,这个类用于被具体的装饰类继承,book类的指针对象用来在运行时动态的选择装饰哪一个具体的构件,这里指数学书或英语书。
这里说一下为什么类里面包含了book类的指针对象,还要继承book:继承是为了接口的规范性,让子类必须重写父类的接口,包含是为了在运行时动态的加载具体的构件,这样的设计非常巧妙,这也是这个模式的核心所在。

class DecratorBook : public book
{
public:
	DecratorBook(book *book1);
	~DecratorBook();

	/* 运行时决定是哪种book */
	book *my_book;
};

DecratorBook::DecratorBook(book *book1):my_book(book1)
{
}

DecratorBook::~DecratorBook()
{
}

** 两个具体装饰类 **
具体装饰类继承自抽象装饰类,在构造函数中传入book的指针对象来初始化父类的my_book指针,动态的选择装饰哪一个具体构件,在这里是数学书或者英语书
这里我们把读书和买书当成是对书的功能的扩展。

class GetBook : public DecratorBook
{
public:
	GetBook(book* my_book);
	~GetBook();

	virtual void name(char *p_name);
	virtual int page();
	virtual int price();
};

GetBook::GetBook(book* my_book) : DecratorBook(my_book)
{
}

GetBook::~GetBook()
{
}

void GetBook::name(char *p_name)
{
	char name[100];
	my_book->name(name);

	printf("I get book %s , %d ¥ \n", name, my_book->price());
}

int GetBook::page()
{
	printf("this book page is %d \n", my_book->page());
	return my_book->page();
}

int GetBook::price()
{
	printf("this book price is %d \n", my_book->price());
	return  my_book->price();
}

class ReadBook : public DecratorBook
{
public:
	ReadBook(book* my_book);
	~ReadBook();

	virtual void name(char *p_name);
	virtual int page();
	virtual int price();
};

ReadBook::ReadBook(book *my_book) : DecratorBook(my_book)
{
	
}

ReadBook::~ReadBook()
{
}

void ReadBook::name(char *p_name)
{
	char name[100];
	my_book->name(name);

	printf("I read book %s , %d pages \n", name, my_book->page());
}

int ReadBook::page()
{
	printf("this book page is %d \n", my_book->page());
	return my_book->page();
}

int ReadBook::price()
{
	printf("this book price is %d \n", my_book->price());
	return my_book->page();
}

main函数测试

int main()
{
	book* mathbook = new MathBook;
	book* englishbook = new EnglishBook;

	GetBook *getbook = new GetBook(mathbook);
	getbook->name(NULL);	

	ReadBook *readbook = new ReadBook(mathbook); 
	readbook->name(NULL);
	return 0;
}

测试结果:

两种模式的比较:

  • 使用继承
    可以看到随着组合的增加,子类的个数成倍的增加

  • 使用Decrator
    对于每一种书扩展的动作是一样的,不需要每次都继承一个子类,可以把这些动作都提取出来,减少了代码的重复性,不管有多少种组合方式,只需要在实用的时候动态的加载具体的构件即可。

Decorator类在接口上表现为is-a Component的继承关系,即 Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类,这里指具体的英语书或者数学书
继承是为了接口的规范性,组合是为了动态的加载具体的构件,在代码中看到即有继承又有组合的方式,大部分都是使用了Decrator模式


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

桥接模式 上一篇
观察者模式 下一篇