C++设计模式——装饰者模式
C++设计模式——装饰者模式
概念
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
场景描述
继续讲个手机的例子,假如现在有苹果手机和诺基亚手机,我们新买到之后需要给他们贴膜,套壳等。但是不同人对手机壳的选择不同。如果要用程序来描述这一场景,我们该怎么实现呢?
一步一步进化到装饰者模式
我们可以很自然的想到,手机是个抽象类,无论是苹果手机还是诺基亚手机都继承自手机这个抽象类。如果我们需要简单装饰的苹果手机的时候,只需要继承苹果手机,然后实现其装饰功能即可。同样的我们如果需要复杂装饰的苹果手机,再继承苹果手机,实现其装饰功能就好。这样实现的类图如下:
这种实现是不是很熟悉,和桥接模式中的场景实现如出一辙了。那么这种实现的问题也就很清楚了:
如果有很多手机都需要同样的一种装饰,我们需要同样的实现很多次,代码没办法复用。
如果我们要修改手机的装饰,那么就必须要通过修改原来装饰的方法来实现,而不是直接给它换一套装饰。
那么根据桥接模式的经验,我们只需要将装饰和手机分离就好,抽象的装饰者类就像一个染料厂一样,将我们的手机扔进去,出来的时候就是我们想要的装饰。于是我们很快的设计了另外一种方案,类图如下。
猛地一看,和桥接模式简直一摸一样。将可变的手机功能和可变的装饰功能分离,这种实现有什么问题呢?我们从装饰器模式概念来着手:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式主要解决的问题是将原有的功能扩展,或者是将责任附加到对象上。装饰者模式本身它不会去改变被装饰着的功能和现有的职责。所以我们再看类图,就会发现如下问题:
- 既然装饰者模式不改变原有的功能和职责,那么手机中本身具有的Function和Surface在装饰者中应该是存在的。但是如果直接聚合的话,Function和Surface需要自己在装饰者类中实现,就算不实现或者实现成别的,也不会出错。
针对装饰者模式要解决的问题,我们只需要在桥接模式的基础上,将装饰者模式和被装饰者之间建立继承关系,这样装饰者中就必须实现被装饰者的抽象方法。如此便克服了用桥接模式实现这类功能的问题。装饰者模式的类图如下:
代码实现
//phone.h
#ifndef PHONE_H_
#define PHONE_H_
#include <iostream>
#include <string>
using namespace std;
#ifndef DELETE_OBJECT
#define DELETE_OBJECT(p) {if(NULL != (p)){delete (p); (p) = NULL;}}
#endif
class Phone//抽象的手机类
{
public:
virtual std::string Function() = 0;
virtual std::string Surface() = 0;
};
class iPhone : public Phone//苹果手机
{
public:
virtual std::string Function()
{
return "iPhone original function";
}
virtual std::string Surface()
{
return "iPhone original surface";
}
};
class Nokia : public Phone//诺基亚手机
{
public:
virtual std::string Function()
{
return "Nokia original function";
}
virtual std::string Surface()
{
return "Nokia original surface";
}
};
class Decorator : public Phone//装饰者基类
{
public:
Decorator(Phone * phone)
{
m_pPhone = phone;
}
virtual ~Decorator()
{
DELETE_OBJECT(m_pPhone);
}
virtual std::string Function()
{
return m_pPhone->Function();
}
virtual std::string Surface()
{
return m_pPhone->Surface();
}
protected:
Phone* m_pPhone;
};
class iPhoneDecorator : public Decorator//苹果手机装饰者
{
public:
iPhoneDecorator(Phone* phone) : Decorator(phone){}
virtual std::string Function()
{
return m_pPhone->Function();
}
virtual std::string Surface()
{
return m_pPhone->Surface() + std::string("+ iPhone's protective film and cover");
}
};
class NokiaDecorator : public Decorator//诺基亚手机装饰者
{
public:
NokiaDecorator(Phone* phone) : Decorator(phone){}
virtual std::string Function()
{
return m_pPhone->Function();
}
virtual std::string Surface()
{
return m_pPhone->Surface() + std::string("+ Nokia's protective film and cover");
}
};
#endif
//main.cpp
#include <iostream>
#include "Phone.h"
using namespace std;
#ifndef DELETE_OBJECTS
#define DELETE_OBJECTS(p) {if(NULL != (p)){delete (p); (p) = NULL;}}
#endif
int main()
{
//苹果手机最原始的样子和最原始的功能
Phone* piPhone = new iPhone();
cout << "My iPhone's Function is: " << piPhone->Function().c_str() << endl;
cout << "My iPhone's Surface is: " << piPhone->Surface().c_str() << endl << endl;
//诺基亚手机最原始的样子和最原始的功能
Phone* pNokia = new Nokia();
cout << "My Nokia's Function is: " << pNokia->Function().c_str()<< endl;
cout << "My Nokia's Surface is: " << pNokia->Surface().c_str()<< endl << endl;
//装饰之后苹果手机功能不变,样子加了保护膜和壳。
Phone* pDecoratoriPhone = new iPhoneDecorator(piPhone);
cout << "My Decorator iPhone's Function is: " << pDecoratoriPhone->Function().c_str() << endl;
cout << "My Decorator iPhone's Surface is: " << pDecoratoriPhone->Surface().c_str() << endl << endl;
//装饰之后诺基亚手机功能不变,样子加了保护膜和壳。
Phone* pDecoratorNokia = new NokiaDecorator(piPhone);
cout << "My Decorator Nokia's Function is: " << pDecoratorNokia->Function().c_str() << endl;
cout << "My Decorator Nokia's Surface is: " << pDecoratorNokia->Surface().c_str() << endl << endl;
DELETE_OBJECTS(piPhone);
DELETE_OBJECTS(pNokia);
DELETE_OBJECTS(pDecoratoriPhone);
DELETE_OBJECTS(pDecoratorNokia);
return 0;
}
优缺点
优点:
可以动态的扩展一个对象的功能。
-
继承了桥接模式的特点,装饰者类和被装饰对象可以独立的变化。
缺点:
会产生很多对象,提高了系统复杂性。
由于比继承更加灵活,所以也更容易出错。对多次装饰的对象,排错困难会很大。
适用场景
-
桥接模式和装饰者模式的区别(个人见解,欢迎指正)
桥接模式主要解决的问题是将抽象和实现分离,通过接口来将两个或者更多易变的模块解耦。而装饰者模式主要解决的问题是不改变原有功能的基础上附加功能或职责。重点是在原有功能的基础上。
-
桥接模式中连接的两个或者多个易变模块之间不一定要有多大的关系,它可以将任何在两个维度或者更高维度以上变化的模块分离。而装饰者模式中装饰者和被装饰者的关系必须得继承。
个人觉得只要将装饰者模式的继承理解清楚,那么和桥接模式的区别就了然于胸。
上一篇: C#设计模式之:装饰模式
下一篇: C# 设计模式———装饰器模式