结构型模式
- 结构型模式
- 1. Adapter(适配器模式)
- 2. Bridge(桥接模式)
- 3.Composite(组合模式)
- 4.Decorator(装饰模式)
- 5.Facade(外观模式)
- 6.Flyweight(享元模式)
- 7.Proxy(代理模式)
- 模式之间的联系
结构型模式
1. Adapter(适配器模式)
适配器模式定义:
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本因接口不兼容而无法一起工作的类能够协同工作。
核心思想:
适配器模式允许将不兼容的接口转换为兼容的接口,使得原本无法协同工作的类能够一起工作。它通过引入适配器类来连接目标接口和原始接口,实现接口的转换和适配。适配器模式提供了一种灵活的方式来集成和复用现有的代码,并支持系统的可扩展性和维护性。
适配器模式优点:
- 提供了不同接口之间的透明转换,使得原本不兼容的类能够协同工作。
- 可以重用现有的类或对象,无需修改其源代码。
- 支持开闭原则,因为可以通过引入新的适配器来适配新的类,而不影响现有的客户端代码。
适配器模式常见的应用场景包括:
- 在使用第三方库或组件时,通过适配器将其接口转换为符合自己系统要求的接口。
- 在系统升级或重构中,用于兼容原有接口和新接口之间的差异。
- 在多态性的应用中,用于将不同子类的接口统一成父类的接口。
适配器模式通常涉及以下几个角色:
目标接口(Target Interface):客户端所期望的接口,适配器将原始接口转换为目标接口。
原始接口(Adaptee):需要被适配的现有类或对象,它定义了客户端无法直接使用的接口。
适配器(Adapter):充当连接目标接口和原始接口之间的桥梁,实现目标接口并持有原始接口的引用。
抽象场景:
有一个绘图编辑器,这个编辑器允许用户可以自由绘制各种图形(点、线、多边形、编辑框等)来生成图片或图表。编辑器的关键抽象是图形对象Shape类,并且编辑器需要为每种图形定义一个Shape的子类,例如LineShape类对应直线。现在考虑到需要做一个可以显示和编辑正文的复杂图形类TextShape,同时假设已存在一个成品用于显示和编辑正文的TextView类,理想的情况是可以通过复用TextView类去实现TextShape类。但是TextView类设计之初并未考虑到Shape类的接口,因此TextView类和TextShape类不能互换。
在这种场景下,可以使用两种方式去协同两个类的工作:1)TextShape继承Shape类的接口和TextView的实现。或2)将TextView的实例作为TextShape的组成部分。这两种方式正好对应着Adapter模式的类和对象版本,我们将TextShape称为适配器。
类版本结构图
上面的结构图展示了类适配器实例,Adapter继承Target的接口,并继承了Adaptee来实现接口。Adapter对Target的接口和Adaptee的功能进行了适配,因此Client可以直接通过Target来使用Adaptee的功能。
对象版本结构图:
上面的结构图展示了对象适配器实例,它说明了在Shape类中声明的BoundingBox接口如何被转换为TextView类中定义的GetExtent接口。由于TextShape通过组合TextView的实例text,以此来对TextView和Shape的接口进行了匹配,因此图像编辑器可以复用原来并不兼容的TextView类。
示例伪代码
//目标接口
class Shape
{
virtual void BoundingBox(int& left, int& right);
}
//原始接口
class TextView
{
void GetExtent(char& left, char& right){...};
}
//类适配器
class TextShape : public Shape, private TextView
{
virtual void BoundingBox(int& left, int& right)
{
char l, r;
get_int_to_char(left, right, l, r);
GetExtent(l, r);
}
}
//对象适配器
class TextShape2 : public Shape
{
TextShape2(TextView* t):_text(t){...}
virtual void BoundingBox(int& left, int& right)
{
char l, r;
get_int_to_char(left, right, l, r);
_text->GetExtent(l, r);
}
TextView* _text
}
2. Bridge(桥接模式)
桥接模式定义:
桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象部分与实现部分分离,使它们可以独立变化。桥接模式通过组合而不是继承的方式实现这种分离,从而提供了更大的灵活性。
核心思想:
桥接模式是将抽象部分与实现部分解耦,使它们可以独立地变化。抽象部分通过持有实现部分的引用来委派具体操作,从而将抽象部分与实现部分解耦。这种解耦使得抽象部分和实现部分可以独立地进行扩展和变化,而不会相互影响。
总结来说,桥接模式通过将抽象部分和实现部分解耦,提供了更大的灵活性和可扩展性。它遵循了设计原则中的分离关注点的思想,使得系统的组件可以独立变化而不相互影响。桥接模式适用于存在多个维度变化的场景,并能够提高系统的可维护性和可扩展性。
桥接模式优点:
- 分离抽象和实现,使它们可以独立地进行变化和扩展。
- 提高了系统的灵活性和可扩展性,因为抽象部分和实现部分可以独立演化。
- 遵循开闭原则,通过引入新的抽象部分或实现部分来扩展系统,而无需修改现有代码。
桥接模式常见的应用场景包括:
- 当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。例如一个跨平台的视频播放器,需要考虑不同的平台和不同的视频格式两种维度,并且这两种维度都可以不断扩展,通过桥接可以实现这两个维度的不同组合。
- 在操作系统的驱动程序开发中,将驱动程序与操作系统进行解耦,使得不同的驱动程序可以在不同的操作系统上工作。
在桥接模式中,通常涉及以下几个角色:
- 抽象部分(Abstraction):定义了抽象类或接口,它包含了对实现部分的引用,并且定义了基于实现接口的高层操作。
- 具体抽象部分(Concrete Abstraction):继承抽象部分,实现了抽象类或接口,并通过调用实现部分的方法来完成高层操作。
- 实现部分(Implementor):定义了实现类或接口,提供了基本操作的接口。
- 具体实现部分(Concrete Implementor):实现了实现部分的接口,并提供了具体的操作。
抽象场景:
在学习桥接之前,如果一个抽象接口有多个实现时,通常用继承的方式。抽象类定义抽象接口,而具体的子类给出不同的实现。但是此方法有时不太灵活,继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立的进行修改、扩充和重用,尤其在抽象具有多维度的情况下,比如抽象下面这样一个场景:
开发一个跨平台的视频播放器,播放器支持的平台有 IOS ,Android ,会有如下继承方式:
假设现在播放器除了支持不同的平台外,还需要支持多种格式的视频(多维度),例如map4,FLV,则会有如下继承方式:
随着平台的种类和视频的类型不断的扩充,类关系的维护将会变得更加复杂。
除此之外,继承机制使得代码与平台相关,每当播放器支持一个新平台时,必须要实例化一个具体的类,这个类有特定的实现部分,这使得很难将代码移植到其他平台去。
Bridge模式解决以上问题的方法是,通过将平台和视频格式分别放在独立的类层次结构中,将抽象与实现解耦 , 通过组合在抽象与实现之间搭建桥梁。桥接模式使用组合而不是继承。如下所示:
示例伪代码
class Platform
{
Platform(Vedio* v):m_vedio(v){};
virtual bool openVedio() = 0 ;
Vedio * m_vedio;
}
class LinuxPlatform : public Platform
{
bool openVedio()
{
m_vedio->openVedio();
m_vedio->showVedio();
}
}
class MacPlatform : public Platform
{
bool openVedio() {...}
}
class Vedio
{
virtual bool openVedio() = 0;
virtual bool showVedio() = 0;
}
class FlvVedio : public Vedio
{
bool openVedio() {...}
bool showVedio() {...}
}
class MP4Vedio : public Vedio
{
bool openVedio() {...}
bool showVedio() {...}
}
//test
int test()
{
// 在 Linux 平台打开 FLV 视频
Platform *linuxPlatform = new LinuxPlatform(new FlvVedio());
linuxPlatform.openVedio();
// 在 mac 平台打开 MP4 视频
Platform macPlatform = new MacPlatform(new MP4Vedio());
macPlatform.openVedio();
}
3.Composite(组合模式)
模式定义:
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
通俗点来说就是,定义包含基本对象和组合对象的类层次结构,基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,并且可以这样不断递归。在使用过程中,用到基本对象的地方可以直接替换为组合对象。
组合模式构成:
- 组件(Component):定义了组合对象和叶子对象的公共接口,可以为叶子对象和组合对象提供默认的实现。
- 叶子(Leaf):表示组合中的叶子对象,它没有子对象。
- 组合(Composite):表示组合中的组合对象,它可以包含子对象。组合对象通常会实现组件接口,并提供了增加、删除和获取子对象的操作。
各个构成部分的协议方式:用户通过组件类的接口与组合结构中的对象进行交互,如果接受者是叶子节点,则直接处理;如果是组合,则通常将请求发给它的子部件,并在转发之前做一些辅助操作。
组合模式工作原理:
组合模式通过使用递归的方式组合对象,形成树形结构。这使得客户端可以像操作单个对象一样操作组合对象,无需关心对象是叶子对象还是组合对象。
组合模式的核心思想是组合对象和叶子对象具有相同的接口,这样客户端可以一致地对待它们。当客户端调用组合对象的操作时,组合对象会将请求传递给它的子对象,直到叶子对象处理请求或者传递给其他子对象。
模式特点
定义了包含基本对象和组合对象的类层次结构,基本对象可以被组合成更复杂的组合对象,而这个对象又可以被组合,这样不断的递归下去。客户代码中可以一致的使用基本对象和组合对象,即任何使用了基本对象的都可以替换为复杂对象,从而简化了代码的复杂性。
常见的应用场景包括
- 需要表示对象的部分-整体层次结构,并希望客户端能够以一致的方式处理单个对象和组合对象时。
- 在无需区分组合对象和叶子对象的情况下,对它们进行操作,并且对所有对象执行相同的操作时。
- 在不增加新的子类的情况下,能够组合对象和叶子对象以形成新的结构。
抽象场景
考虑这样一种场景:现在有一个绘图编辑器,用户可以使用编辑器中的简单组件,也可以将多个简单组件组合成一个更大的组件。
一个简单的实现方法是,为Line、Text这样的图元定义一些类,另外定义一些类作为这些图元的容器类。但是这样的设计思路存在一个问题,使用这些类的代码需要区别的对待图元对象与容器对象,而实际上大多数情况下用户认为它们是一样的(简单图形和组合图形都是图形)。对这些类区别对待,使得程序更加复杂。
使用组合模式递归的去组合这些图形,使得用户不必对这些类进行区别,如下图所示:
Graphic即组件(Component),是一个定义了一系列抽象接口的抽象类(编辑器中的图形或组合图形的基类),既可以表示图元,又可以表示图元的组合。
子类Line、Rectangle和Text定义了一个图元对象,即叶子(Leaf),这些类都实现了基本的操作Draw。由于图元没有子图形,因此它们不需要实现与子类相关的操作。
子类Picture定义了一个Graphic对象的集合,即组合(Composite),Picture内部实现了操作子部件的方法(添加、移除子部件等)。
下图是一个典型的由Graphic对象递归组合的对象结构:
示例伪代码
class Graphic
{
virtual void Draw();
virtual bool Add(Graphic* g);
virtual bool Remove(Graphic *g);
}
//叶子节点类
class Line : public Graphic
{
void Draw(){ //draw line ... }
}
class Rect : public Graphic {...}
//组合类
class Picture : public Graphic
{
virtual void Draw(){
for g in _graphic
g.Draw();
}
virtual bool Add(Graphic* g) { _graphic.add(g); }
virtual bool Remove(Graphic *g) { _graphic.remove(g); }
list<Graphic*> _graphic;
}
//test
Line *l = new Line();
Rect *r = new Rect();
Picture *p = new Picture();
p.add(l);
p.add(r);
Picture *p1 = new Picture();
p1.add(p);
p1.Draw();
4.Decorator(装饰模式)
装饰模式定义
装饰模式(Decorator Pattern),动态的给一个对象添加额外的职责,它允许你在不改变已有对象的基础上,动态地将新功能附加到对象上。
装饰模式构成
- 组件(Component):定义了被装饰对象和装饰对象的公共接口,可以是抽象类或接口。
- 具体组件(Concrete Component):实现了组件接口,是被装饰的对象。它是装饰的起点。
- 装饰器(Decorator):继承或实现了组件接口,并持有一个组件对象的引用。装饰器可以在运行时动态地添加附加行为。
- 具体装饰器(Concrete Decorator):具体实现装饰器接口,在运行时为组件添加具体的附加行为。
装饰模式工作原理
装饰模式通过使用组合的方式,将装饰器对象包裹在被装饰对象周围,形成一条装饰链。每个装饰器对象都增加了新的行为或功能,但仍然保持了组件的原始接口。
当客户端通过组件接口调用方法时,请求会首先传递给最外层的装饰器,然后逐层传递给装饰链中的下一个装饰器或最终的具体组件。每个装饰器可以在传递请求之前或之后,自定义处理逻辑,从而实现了动态地添加附加行为。
装饰模式特点
这种方式使得客户端无需关心装饰器和具体组件的具体细节,可以透明地使用装饰后的对象。
装饰模式可以使代码更加灵活、可扩展和易于维护,同时避免了类爆炸问题。
装饰常见的应用场景包括
- 当需要在不修改现有对象结构的情况下,动态地添加功能或行为时。
- 当无法或不方便使用子类继承来扩展对象功能时。
- 当需要为对象的不同组合方式提供不同的行为时。
装饰抽象场景
现在有一个图形界面工具箱软件,需要允许对任意图形组件添加一些特性,例如加个边框,或者加一个窗口滚动条等。
假如说使用继承机制来实现这种效果,需要新类继承图形组件类,并附加一些需要的特性。当组件和特性数量增加时,会导致类的数量剧增。
另一种较为灵活的方式是,将组件嵌入另一个类中,由这个类做添加边框操作。我们称这个嵌入的类为装饰。这个装饰和它所装饰的组件接口一致,因此在使用上具有一致性。装饰将客户的请求转发给该组件,并且在转发前后执行一些额外的动作。类似组合模式,组件可以嵌套多个装饰,从而添加任意多的功能。
上图展示了,一个显示正文的窗口组件,添加了一个滚动条ScrollDecorator,和一个粗黑边框BorderDecorator。
下图展示上述窗口组件的类图关系,
VisualComponent,即组件,描述了可视对象的抽象类,它定义了绘制和事件处理相关接口。
TextView,即具体的组件,实现了组件的绘制接口,它的实例是被装饰的对象。
Decorator,即装饰器,实现了组件的相关接口,并持有一个组件对象的引用component。
ScrollDecorator和BorderDecorator,是具体的装饰器,装饰器接口的实现,并添加了附加行为。
示例伪代码
//组件类
class VisualComponent
{
virtual void Draw();
}
//装饰器类
class Decorator : public VisualComponent
{
Decorator(VisualComponent* t) : _component(t) { } ;
virtual void Draw()
{
_component->Draw(); //客户请求转发给具体的组件
}
VisualComponent* _component;
}
//具体的组件类
class TextView : public VisualComponent
{
void Draw() { ... }
}
//具体的装饰器类
class BorderDecorator : public Decorator
{
BorderDecorator(VisualComponent *v)
void DrawBorder() { ... }
virtual void Draw()
{
Decorator::Draw();
DrawBorder();//额外的操作
}
}
//生成一个普通的窗口
TextView * text = new TextView();
//生成一个带有边框的窗口
BorderDecorator *bd = new BorderDecorator(c);
bd->Draw();
5.Facade(外观模式)
模式定义
外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个外观对象,用于访问子系统中的一组接口。外观模式通过将客户端与复杂的子系统解耦,简化了客户端与子系统之间的交互。
模式构成
- 外观(Facade):提供了一个简单的对外接口,用于与子系统进行交互。外观对象封装了对子系统中各个组件的调用,隐藏了子系统的复杂性。
- 子系统(Subsystem):由多个相关的类组成,实现了子系统的功能。子系统可以包含多个类和接口,但客户端只需要通过外观与外观对象进行交互,而不需要直接与子系统的类进行通信。
模式工作原理
外观模式通过引入一个外观对象作为客户端与子系统之间的中间层,简化了客户端与子系统之间的交互过程。客户端通过与外观对象进行交互来完成所需的功能,而不需要直接了解和调用子系统中的多个类和接口。外观对象将客户端的请求委派给子系统中的适当对象,协调子系统中的各个组件来完成请求。
模式特点
通过使用外观模式,客户端只需要与外观对象进行交互,无需了解子系统的内部结构和复杂性。这样可以降低客户端的复杂性,减少对子系统的依赖,并提供了一种简单、统一的接口来访问子系统的功能。
常见的应用场景包括
- 当存在一个复杂的子系统,其中包含多个类和接口,并且客户端需要与该子系统进行交互时。
- 当需要简化客户端与子系统之间的依赖关系,降低客户端的复杂性时
- 当希望将子系统的实现细节隐藏起来,提供一个简单的接口给客户端使用时。
抽象场景
考虑到这样一种场景,代码工具IDE上都有一键构建程序的按钮,例如Visual Studio,当点击这个按钮后IDE就会将代码编译链接成一个二进制执行包。但是程序编译的过程是复杂的,例如C语言的代码到可执行程序会经过1)语法分析,2)编译,3)汇编,4)链接等多个步骤。
示例伪代码
#include <iostream>
using namespace std;
//语法分析子系统
class SyntaxParser
{
public:
void doSyntaxParser();
};
//编译模块子系统
class CompileCode
{
public:
void doCompileCode();
};
//生成汇编代码子系统
class GenerateAssemblyCode
{
public:
void doGenerateAssemblyCode();
};
//链接生成可执行应用程序文件或者库文件子系统
class LinkSystem
{
public:
void doLinkSystem();
};
/*
* 外观类将编译过程涉及的所有子系统
* 所做的工作包装成一个高层的API
* doCompile()给客户端使用
*/
class Facade
{
public:
void doCompile() {
SyntaxParser syntaxParser;
CompileCode compilecode;
GenerateAssemblyCode generateAssemblyCode;
LinkSystem linkSystem;
syntaxParser.doSyntaxParser();
compilecode.doCompileCode();
generateAssemblyCode.doGenerateAssemblyCode();
linkSystem.doLinkSystem();
}
};
//客户端使用
int main()
{
Facade facade;
facade.doCompile();
return 0;
}
6.Flyweight(享元模式)
模式定义
享元模式,旨在通过共享对象来有效地支持大量细粒度的对象。该模式通过共享相似对象的公共部分,减少内存使用和对象创建的开销,从而提高系统的性能和效率。
模式构成
享元模式包含以下几个关键角色:
- 享元工厂(Flyweight Factory):负责创建和管理享元对象。它维护一个享元池(Flyweight Pool),用于存储已创建的享元对象,并根据需要返回相应的享元对象。
- 享元(Flyweight):表示共享的对象,包含对象的
内部状态和外部状态
。内部状态是不变的,可以被多个对象共享;外部状态是可变的,由客户端在使用时传递给享元对象。 - 具体享元(Concrete Flyweight):实现享元接口,并为内部状态提供具体的实现。具体享元对象通常是可共享的,并且可以在多个上下文中被共享。
- 非共享具体享元(Unshared Concrete Flyweight):并不共享的具体享元对象,它通常包含不可共享的额外状态,无法被其他对象共享。注意,非共享具体享元对象是对共享对象的补充,用于处理一些特殊情况或个性化定制需求(导致对象在一些特定状态无法共享),但它并不是必须的。
模式工作原理
享元模式的核心思想是在需要大量细粒度对象时,通过共享相似部分来节省内存和创建对象的开销。
当客户端请求一个享元对象时,享元工厂首先检查是否已经有可用的共享对象。如果有,则返回已有的享元对象;如果没有,则创建一个新的享元对象并将其存储在享元池中,以备将来使用。客户端可以通过传递外部状态的方式向享元对象传递特定的上下文信息。
通过共享对象的内部状态,多个享元对象可以共享相同的数据,从而节省了内存使用。外部状态则可以在使用时进行传递,保证了享元对象的可变性和上下文特定性。
模式特点
享元模式可以在需要大量相似对象的场景中提供显著的性能和内存优化。但需要注意的是,享元模式引入了对象共享的概念,可能会增加系统的复杂性和维护成本。因此,应该根据具体情况进行权衡和使用。
常见的应用场景包括
- 系统中存在大量细粒度的对象,并且创建、销毁这些对象的开销很大。
- 对象的大部分状态可以被视为内部状态,可以被多个对象共享。
- 对象的外部状态可以在使用时进行传递,并且不影响对象的内部状态。
- 使用享元模式可以减少内存使用,并提高系统的性能和效率。
抽象场景
现在有一个简单的字符文档编辑器,面向对象的做法通常是使用对象来表示嵌入的成分,因此我们首先想到的是用对象来表示文档中的每个字符,这会极大的提高程序的灵活性(例如当字符有不同字体,或者颜色时)。但是这种设计有一个明显的缺点在于内存耗费太大,即使一个中等大小的文档也可能有成百上千的字符对象。所以通常并不会对每个字符都用一个对象来表示。享员模式正是解决这种问题的。
具体的做法为,文档编辑器可以为字母表中每一个字母创建一个享元对象(假设文档编辑器只能使用字母),每个享元对象存储一个字符代码,但它在文档中的位置由正文排版算法决定。此时字符代码就是内部状态(不变且共享),位置信息就是外部状态,由排版算法决定。
在逻辑上,文档中出现的每个字符都有一个对象与之对应。然而,在物理上,出现在文档不同位置多个相同字符共享同一个享元对象,如下图所示:
由于不同的字符对象数目远小于文档中的字符总数,因此极大的减少了内存的使用。
示例伪代码
以文档编辑器为例子:
//享元类
class Glyph
{
//位置p表示字符的外部属性,c对象记录了字符的外部状态,c保存了一张字符和其在文档中位置的映射表
virtual void Draw(Point p, Conetext* c);
}
//具体享元类
class Character : public Glyph
{
Character(char c);
void Draw(Point p, Conetext* c)
{/*通过排版算法,将字符和其位置写入关联表中*/}
//内部状态,保存的字符
char _charcode;
}
//享元工厂类
GlyphFactory
{
//如果存在字符立马返回,不存在创建一个,并存入_character中
virtual Character *CreateCharacter(char c)
{
if(!_character[c])
_character[c] = new Character(c);
}
//保存了已有的具体享元对象,这里简单表示为一个数组
Character *_character[128];
}
//使用,写一个字符b到第一行第一列
GlyphFactory f;
Conetext *ct = new Conetext();
Point p(1,1);
char c = 'b';
Character *ch = f.CreateCharacter(c);
ch.draw(p, ct);
7.Proxy(代理模式)
模式定义
代理模式(Proxy Pattern)允许你提供一个代理对象来控制对其他对象的访问。在代理模式中,一个类代表另一个类的功能。
模式构成
- Subject(抽象主题):定义了RealSubject和Proxy的共同接口,这样在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject(真实主题):定义了Proxy所代表的真实对象。
- Proxy(代理):保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理可以用来替代实体。
调用结构图:
模式工作原理
代理模式使得能够实现对对象的访问控制和额外的功能,同时不改变原始对象的结构。
模式特点
代理模式通过引入代理对象来控制对真实对象的访问,实现了对对象的保护、功能增强、延迟加载等特性,从而使系统更加灵活、安全和高效。
常见的应用场景
- 远程代理(Remote Proxy),当客户端需要访问远程对象时,可以使用代理模式来隐藏远程对象的复杂性。
- 虚拟代理(Virtual Proxy),在对象的创建和初始化开销较大时,可以使用代理模式延迟对象的实例化,只有在真正需要时才创建真实对象。
- 保护代理(Protection Proxy),代理可以控制对真实对象的访问权限,例如限制某些用户对对象的访问,或者在调用真实对象之前进行安全检查。
- 日志记录代理(Logging Proxy),代理可以用于记录方法的调用信息,如日志记录、性能监控等。
- …
抽象场景
考虑这样一个场景,在一个文档编辑器中嵌入一个图像编辑器,并且打开文档必须很迅速。这在图形对象很小的时候不会有什么问题,但是对于有些图形对象创建开销很大时,就会出现无法迅速打开的问题。这一限制条件意味着对于每一个大开销对象,应该根据需要进行创建。那应该如何根据需要去延迟创建对象而不会使得编辑器的实现复杂化?
代理模式正是解决这个问题的,在文档编辑器中使用图像代理对象,只有当文档编辑器激活图像代理对象的Draw操作时,图像代理对象才创建真正的图像对象。从而达到延迟创建对象的效果。
示例伪代码
//抽象主题,抽象图像对象
class Graphic
{
virtual void Draw() = 0;
virtual Graphic* GetExtent() = 0;
};
//真实主题,真实的图像对象
class Image : public Graphic
{
Image(string filename){...//加载}
void Draw(){...}
Graphic* GetExtent(){ return this;}
};
//代理,图像对象的代理
class ImageProxy : public Graphic
{
void Draw()
{
if(_image == NULL)
_image = new Image("pictrue");
_image->Draw();
}
Graphic* GetExtent()
{
if(_image==NULL)
return NULL ;
else
return _image->GetExtent();
}
Image* _image; //核心
};
//文档编辑器
class TextDocument
{
int insert(Graphic* image);
...
Graphic* _image[num];
}
//使用
TextDocument* text = new TextDocument;
//将代理图像插入到文档编辑器中,后续真正使用时,调用Draw延迟加载图像对象
TextDocument->insert(new ImageProxy());
模式之间的联系
- 适配器模式和桥接模式类似,但是桥接模式的目的是:将接口和实现部分分离,从而可以独立的对它们加以改变;而适配器意味着改变一个已有对象的接口。
- 组合模式和装饰模式在类的结构层次上相似(当装饰模式仅有一个装饰扩展时,两者的层次结构甚至是一样的)。在类层次结构上它们都使用一个公共的父类。两种模式有时也可以配合使用。
- 装饰与组合的不同之处:装饰模式旨在给对象添加一些额外的职责,而不关注对象的聚集。
- 装饰器模式与适配器模式不同之处:装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。
- 装饰器模式与Strategy模式的不同之处:装饰扩展对象职责通过外部,Strategy模式则通过内核(组件内部保存扩展策略)。
- (1)使用继承方式实现,(2)、(3)、(4)使用组合方式实现。
- 享元模式通常和组合模式结合使用。
- 代理模式的主要目的是控制对对象的访问,而装饰模式的主要目的是为对象动态添加新功能。