装饰器(Decrator)模式
“单一职责模式”的适用场景
在软件组件设计中,如果责任划分的不清晰,使用继承得到的结果往往是随需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
场景
某一天,你的老板写一堆log类来分别打印错误信息和警告信息。
class Log{
public:
virtual void WriteLog(const char *data) = 0;
virtual char ReadLog(int number) = 0;
};
class ErrorLog : public Log{
public:
virtual void WriteLog(const char *data){
}
virtual char ReadLog(int number){
}
};
class WarnLog: public Log{
public:
virtual void WriteLog(const char *data){
}
virtual char ReadLog(int number){
}
};
int main(void){
Log *warnLog = new WarnLog();
Log *errorLog = new ErrorLog();
if(Error)
errorLog->WriteLog("某个地方发生Error");
if(Warn)
warnLog->WriteLog("某个地方发生Warn");
//经过很多次调用
//假装忘记delete
return 0;
}
这时候你解决了问题,完美下班。
but 过了几天,你老板发现,这样Log打印好像对性能负担很大,于是乎要求你先用一个Buffer储存这些log,然后再合适的时间打印这些log,于是你要写下面的类
class BufferErrorLog : public ErrorLog{
public:
virtual void WriteLog(const char *data){
//额外的BUffer操作
ErrorLog::WriteLog(data);
}
virtual char ReadLog(int number){
return ReadLog::ReadLog(number);
}
};
class BufferWarnLog: public WarnLog{
public:
virtual void WriteLog(const char *data){
//额外的BUffer操作
WarnLog::WriteLog(data);
}
virtual char ReadLog(int number){
return WarnLog::ReadLog(number);
}
};
int main(void){
Log *warnLog = new BufferWarnLog();
Log *errorLog = new BufferErrorLog();
if(Error)
errorLog->WriteLog("某个地方发生Error");
//经过很多次调用
//在一个合适的时候一次性输出了之前所有的Log
if(Warn)
warnLog->WriteLog("某个地方发生Warn");
//假装忘记delete
return 0;
}
现在视乎又解决了问题,
but又过了几天,你的老板发现直接输出log有点不安全,所以他决定在输出log的时候对log进行加密操作,并且他认为直接无Buffer的输出log是简单有效的,他觉定不舍弃直接输出的类,于是乎WarnLog,ErrorLog,BufferWarnLog,BufferErrorLog 这四个类都应该有一个子类是父类的加密(CryptoWarnLog,CryptoErrorLog,CryptoBufferWarnLog,CryptoBufferErrorLog)
本来这个项目有两个类(WarnLog,errorLog),加了Buffer功能之后这个项目就有了四个类(WarnLog,ErrorLog,BufferWarnLog,BufferErrorLog),再加个加密操作之后这个项目就有了八个类,再加一个功能,这个项目就有16个类,每加一种新的功能,类的数量成指数级(2n)增长。一旦多加几个功能,一个项目100,000类都不是梦。很明显这是不合理的,这时候就需要装饰器模式。
使用装饰器模式
装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象添加功能。在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
//之前的log类,ErrorLog类和WarnLog类这里就不重写了,从Buffer开始
class DecoratorLog :public Log{
public:
DecoratorLog(Log *log) :mLog(log){}
protected:
Log *mLog;
};
//它是一个装饰器
class BufferLog :public DecoratorLog {
public:
BufferLog(Log *log) :DecoratorLog (log){}
virtual void WriteLog(const char *data){
//额外的Buffer操作
mLog->WriteLog(data);
}
virtual char ReadLog(int number){
return mLog->ReadLog(number);
}
};
//它是一个装饰器
class CryptoLog : public DecoratorLog {
public:
CryptoLog(Log *log) :DecoratorLog (log){}
virtual void WriteLog(const char *data){
//额外的加密操作
mLog->WriteLog(data);
}
virtual char ReadLog(int number){
return mLog->ReadLog(number);
}
};
int main(void){
Log *warnLog = new BufferLog(new WarnLog());
Log *errorLog = new BufferLog(new ErrorLog());
if(Error)
errorLog->WriteLog("某个地方发生Error");
//经过很多次调用
//在一个合适的时候一次性输出了之前所有的Log
if(Warn)
warnLog->WriteLog("某个地方发生Warn");
//假装忘记delete
return 0;
}
这时候,我只写了一个BufferLog类,就能实现BufferWarnLog类和BufferErrorLog 类的功能,就算再添加一个加密操作,也只是再写一个CryptoLog 类而已。这样写加几个功能,就加几个类,类的数量就是功能的数量(n)。
这样写代码有多个好处在上面的代码中没有体现
- 在装饰器类里面加一个setMLog方法,同一个装饰器可以重复使用。
- 通过xml配置文件和工厂模式,可以把装饰器的构造通过配置文件来决定,方便后续的更新。
类图
装饰器模式的优点:
-
可以轻松对已存在的对象进行修改和包装,在被装饰者的前面或者后面添加自己的行为,而无需修改原对象。
-
可以动态、不限量地进行装饰,可以更灵活地扩展功能。
相对地,装饰器模式有很明显的缺点:
-
会加入大量的小类,即使只添加一个功能,也要额外创建一个类,使得程序更复杂。
-
增加代码复杂度,使用装饰器模式不但需要实例化组件,还要把组件包装到装饰者中。