结构型设计模式
作者:Mrzhu007
日期:2018-11-26
博客地址:金色世界
适配器、桥接、组合、装饰、外观、享元、代理
装饰
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
介绍
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
示例
// 抽象界面构件类
class Component
{
public:
virtual void Dispaly() = 0;
}
// 窗体类
class Window : public Component
{
public:
virtual void Dispaly()
{
print("显示窗体!");
}
}
// 文本框类
class TextBox : public Component
{
public:
virtual void Dispaly()
{
print("显示文本框!");
}
}
// 列表框类
class ListBox : public Component
{
public:
virtual void Dispaly()
{
print("显示列表框!");
}
}
// 装饰类: 抽象装饰类
class ComponentDecorator : public Component
{
private:
Component* m_pComponent;
public:
ComponentDecorator(Component* pComponent_)
{
m_pComponent = pComponent_;
}
virtual void Dispaly()
{
m_pComponent->Dispaly();
}
}
// 滚动条装饰类
class ScrollBarDecorator : public ComponentDecorator
{
public:
ScrollBarDecorator(Component* pComponent_) : ComponentDecorator(pComponent_)
{}
virtual void Dispaly()
{
SetScrollBar();
__super::Dispaly();
}
protected:
void SetScrollBar()
{
print("增加滚动条!")
}
}
// 黑色边框装饰类
class BlackBorderDecorator : public ComponentDecorator
{
public:
BlackBorderDecorator(Component* pComponent_) : ComponentDecorator(pComponent_)
{}
virtual void Display()
{
SetBlackBorder();
__super::Dispaly();
}
protected:
void SetBlackBorder()
{
print("增加黑色边框!")
}
}
// 客户端使用
int main()
{
Component* _pComponent = new Window(); // 创建窗体
Component* _pSBComponent = new ScrollBarDecorator(_pComponent); // 加滚动条的窗体
Component* _p = new BlackBorderDecorator(_pSBComponent);
_pSBComponent->Display();
}
注:上面为透明装饰者模式
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
适配器
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
介绍
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
对象适配器
示例
// 目标接口
class ScoreOperation
{
public:
virtual int[] Sort(int array_[]) = 0; // 成绩排序
virtual int Search(int array_[], int nKey_) = 0; // 成绩查找
}
// 适配者
class QuickSort
{
public:
int[] QuickSort(int array_[])
{
Sort(array_, 0, _countof(array_) - 1);
return array_;
}
private:
void Sort(int array_[], int p_, int r_);
}
// 二分查找
class BinarySearch
{
public:
int BinarySearch(int array[], int nKey_)
{
// ......
}
}
class OperationAdapter : public ScoreOperation
{
public:
OperationAdapter()
{
m_pSortObj = new QuickSort;
m_pSearchObj = new BinarySearch;
}
virtual int[] Sort(int array_[])
{
return m_pSortObj->QuickSort(array_);
}
virtual int Search(int array_[], int nKey_))
{
return m_pSearchObj->BinarySearch(array_, nKey_);
}
private:
QuickSort* m_pSortObj;
BinarySearch* m_pSearchObj;
}
类适配器 :即适配器继承接口和适配者,语言层级需要支持多重继承
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
组合
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
介绍
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
示例
// 文件抽象类
class File
{
public:
virtual void Add(File* pFile_) = 0;
virtual void Remove(File* pFile_) = 0;
virtual File* GetChild(int nIndex_) = 0;
virtual void KillVirus() = 0;
}
// 图像文件:叶子构件
class ImageFile : public File
{
public:
ImageFile(string strName_) { m_strName = strName_; }
virtual void Add(File* pFile_) { print("不支持该方法!");}
virtual void Remove(File* pFile_) { print("不支持该方法!");}
virtual File* GetChild(int nIndex_) { print("不支持该方法!"); return nullptr;}
virtual void KillVirus()
{
print("对图像文件" + m_srtName + "进行杀毒!");
}
private:
string m_strName;
}
// 文本文件:叶子构件
class TextFile : public File
{
public:
TextFile(string strName_) { m_strName = strName_; }
virtual void Add(File* pFile_) { print("不支持该方法!");}
virtual void Remove(File* pFile_) { print("不支持该方法!");}
virtual File* GetChild(int nIndex_) { print("不支持该方法!"); return nullptr;}
virtual void KillVirus()
{
print("对文本文件" + m_srtName + "进行杀毒!");
}
private:
string m_strName;
}
// 视频文件:叶子构件
class VideoFile : public File
{
public:
VideoFile(string strName_) { m_strName = strName_; }
virtual void Add(File* pFile_) { print("不支持该方法!");}
virtual void Remove(File* pFile_) { print("不支持该方法!");}
virtual File* GetChild(int nIndex_) { print("不支持该方法!"); return nullptr;}
virtual void KillVirus()
{
print("对视频文件" + m_srtName + "进行杀毒!");
}
private:
string m_strName;
}
// 文件容器:容器构件
class Folder : public File
{
public:
Folder(string strName_) { m_strName = strName_; }
virtual void Add(File* pFile_)
{
m_pList.push(pFile_);
}
virtual void Remove(File* pFile_)
{
m_pList.remove(pFile_);
}
virtual File* GetChild(int nIndex_)
{
return m_pList.at(nIndex_);
}
virtual void KillVirus()
{
print("对文件夹" + m_srtName + "进行杀毒!");
for(auto obj : m_pList)
(*obj)->KillVirus();
}
private:
string m_strName;
std::list<File*> m_pList;
}
// 客户端使用
void main()
{
Folder* pFolder1, pFolder2, pFolder3;
pFolder1 = new Folder("资料");
pFolder1 = new Folder("图像");
pFolder1 = new Folder("文本");
ImageFile* pImage1, pImage2;
pImage1 = new ImagerFile("小龙女.jpg");
pImage2 = new ImagerFile("张无忌.gif");
TextFile* pText1, pText2;
pText1 = new TextFile("玉女真经.txt");
pText2 = new TextFile("九阳神功.doc");
pFolder1->Add(pFolder2);
pFolder1->Add(pFolder3);
pFolder2->Add(pImage1);
pFolder2->Add(pImage2);
pFolder3->Add(pText1);
pFolder3->Add(pText2);
pFolder1->KillVirus();
}
使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
注意事项:定义时为具体类。
外观
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
介绍
意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
如何解决:客户端不与系统耦合,外观类与系统耦合。
关键代码:在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
// 文件读取类:子系统
class FileReader
{
public:
String Read(String fileName_){}
}
// 数据加密类:子系统
class CipherMachine
{
public:
String Encrypt(String plainText_){}
}
// 文件保存类:子系统
class FileWirter
{
public:
void Write(String encryptStr_, String fileNameDes_){}
}
// 加密外观类:外观类
class EncryptFacade
{
public:
void FileEncrypt(String fileNameSrc_, String fileNameDes_)
{
String _strPlain = m_pRead->Read(fileNameSrc_);
String _strEncrypt = m_pCipher->Encrypt(_strPlain);
m_pWriter->Write(_strEncrypt, fileNameDes_);
}
private:
FileReader* m_pRead;
CipherMachine* m_pCipher;
FileWriter* m_pWriter;
}
应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口。
享元
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
介绍
意图:运用共享技术有效地支持大量细粒度的对象。
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
如何解决:用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
关键代码:用 HashMap 存储这些对象。
class IgoChessman
{
public:
virtual String GetColor() = 0;
void DisPlay()
{
printf("棋子颜色:" + GetColor());
}
}
class BlackIgoChessman : public IgoChessman
{
public:
virtual String GetColor()
{
return "黑色";
}
}
class WhiteIgoChessman : public IgoChessman
{
public:
virtual String GetColor()
{
return "白色";
}
}
class IgoChessmanFactory
{
public:
static IgoChessmanFactory GetInstance() {
return m_pInstance;
}
static IgoChessman* GetIgoChessman(String color)
{
return (IogChessman*)m_pHt->get(color);
}
private:
IgoChessmanFactory()
{
m_pHt = new Hashtable;
IgoChessman* black, white;
black = new BlackIgoChessman;
white = new WhiteIgoChessman;
m_pHt->put("b", black);
m_pHt->put("w", whtie);
}
static IgoChessmanFactory* m_pInstance = new IgoChessmanFactory();
static Hashtable* m_pHt;
}
// client
int main()
{
IgoChessmanFactory* _pFactory = IgoChessmanFactory::GetInstance();
IgoChessman* _pB1 = _pFactory->GetIgoChessman("b");
IgoChessman* _pB2 = _pFactory->GetIgoChessman("b");
IgoChessman* _pB3 = _pFactory->GetIgoChessman("b");
IgoChessman* _pW1 = _pFactory->GetIgoChessman("w");
IgoChessman* _pW2 = _pFactory->GetIgoChessman("w");
_pB1->Display();
_pB2->Display();
_pB3->Display();
_pW1->Display();
}
// 引入外部状态
class Coordinates
{
public:
int x;
int y;
}
class IgoChessman
{
public:
virtual String GetColor() = 0;
void DisPlay(Coordinates coord)
{
printf("棋子颜色:" + GetColor() + "棋子位置:" + coord.x + coord.y);
}
}
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
代理
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
介绍
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
class Searcher
{
public:
String doSearch(String userID, String keyword);
}
class AccessValidator
{
public:
bool validate(String userID);
}
class Logger
{
public:
void Log(String userID);
}
class RealSearcher : public Searcher
{
public:
String doSearch(String userID, String keyword);
}
class ProxySearcher : public Searcher
{
public:
ProxySearcher()
{
m_pSearcher = new RealSearcher;
}
String doSearch(String userID, String keyword)
{
if(validate(userID))
{
String result = m_pSearcher->doSearch(userID, keyword);
Log(userID);
return result;
}
else
{
return null;
}
}
private:
Log(String userID)
{
m_pLogger = new Logger();
m_pLogger->Log(userID);
}
bool validate(String userID)
{
m_pValid = new AccessValidator();
if(m_pValid->validate(userID))
return true;
else
return false;
}
private:
RealSearcher* m_pSearcher;
AccessValidator* m_pValid;
Logger* m_pLogger;
}
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
桥接
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
介绍
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
关键代码:抽象类依赖实现类。
结构
示例
// 辅助类
class Matrix{};
// 图像类
class Image
{
public:
void SetImageImp(ImageImp imp_)
{
m_imp = imp_;
}
virtual void parseFile(string fileName_) = 0;
protected:
ImageImp m_imp;
}
// 图像
class ImageImp
{
public:
virtual void doPaint(Matrix matrix_) = 0;
}
// window
class WindowsImp : public ImageImp
{
public:
virtual void doPaint(Matrix matrix_)
{
print("windows 绘图")
}
}
....
class PNGImage : public Image
{
public:
void ParseFile(string fileName_)
{
Matrix _m = new Matrix();
m_imp.doPaint(_m);
}
}
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。