1、结构型模式
描述如何将类或者对象结合在一起行成更大的结构,就像搭积木,可以通过简单的积木的组合行成复杂的、功能更为强大的结构。
结构型模式可以分为:类结构型模式和对象结构型模式
- 类结构型模式关心类的组合,由多个类可以组成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
- 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
根据“合成复用原则”,在系统中尽量使用关联关系替代继承关系,因此大部分结构型模式都是对象结构型模式。
包含:
适配器模式(Adapter)
桥接模式(Bridge)
装饰模式(Decorator)
外观模式(Facade)
享元模式(Flyweight)
代理模式(Proxy)
2、适配器模式
2.1 定义
在现实生活总中,经常出现两个对象因接口不兼容不能一起工作的实例,这时需要第三者进行适配。如:翻译、电源适配器、读卡器。
在软件设计中也可能出现:需要开发具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组建成本又很高,这时用适配器模式能很好地解决这些问题。
适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中相关组件的内部结构,所以应用相对较少些。
2.2 模式结构
- 目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者类:它是被访问和适配的现存组件库中的组件接口。
- 适配器类:它是一个转换器,通过继承或则引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器:
对象适配器:
2.3 C++代码
#include <iostream>
#include <string>
using namespace std;
//目标接口
class Target
{
public:
virtual void request() = 0;
};
//适配者接口
class Adaptee
{
public:
void specificRequest()
{
cout << "适配者中的业务代码被调用!" << endl;
}
};
//类适配器
class ClassAdapter :public Target,private Adaptee
{
public:
void request()
{
specificRequest();
}
};
//客户端代码
class ClassAdapterTest
{
public:
static void main()
{
cout << "类适配器模式测试:";
Target *target = new ClassAdapter();
target->request();
}
};
//对象适配器
class ObjectAdapter:public Target
{
private:
Adaptee *adaptee;
public:
ObjectAdapter(Adaptee *adaptee)
{
this->adaptee = adaptee;
}
~ObjectAdapter(){ delete adaptee; }
void request()
{
adaptee->specificRequest();
}
};
//客户端代码
class ObjectAdapterTest
{
public:
static void main()
{
cout << "对象适配器模式测试:";
Adaptee *adaptee = new Adaptee();
Target *target = new ObjectAdapter(adaptee);
target->request();
delete adaptee, target;
}
};
int main()
{
ClassAdapterTest *cat = new ClassAdapterTest();
cat->main();
ObjectAdapterTest *oat = new ObjectAdapterTest();
oat->main();
return 0;
}
2.4 优点
- 将目标类和适配者类解耦,通过引入一个适配器来重用现有的适配者类,而无须修改原有代码。
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码地基础上增加新的适配器类,完全符合“开闭原则”。
2.5 缺点
类适配器模式的缺点:
- 对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和他的子类都适配到目标接口。
对象适配器的缺点:
- 与类适配器模式相比,想要置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂。
2.6 适用环境
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
2.7 扩展
适配者模式可扩展为双向适配器模式。双向适配器类可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口。
#include <iostream>
#include <string>
using namespace std;
//目标接口
class TwoWayTarget
{
public :
virtual void request() = 0;
};
//适配者接口
class TwoWayAdaptee
{
public:
virtual void specificRequest() = 0;
};
//目标实现
class TargetRealize:public TwoWayTarget
{
public:
void request()
{
cout << "目标代码被调用!";
}
};
//适配者实现
class AdapteeRealize:public TwoWayAdaptee
{
public:
void specificRequest()
{
cout << "适配者代码被调用!" << endl;
}
};
//双向适配器
class TwoWayAdapter:public TwoWayTarget,public TwoWayAdaptee
{
private:
TwoWayAdaptee *adaptee;
TwoWayTarget *target;
public:
TwoWayAdapter(TwoWayAdaptee *adaptee)
{
this->adaptee = adaptee;
}
TwoWayAdapter(TwoWayTarget *target)
{
this->target = target;
}
void request()
{
adaptee->specificRequest();
}
void specificRequest()
{
target->request();
}
};
class TwoWayAdapterTest
{
public:
static void main()
{
cout << "目标通过双向适配器访问适配者:";
TwoWayAdaptee *adaptee = new AdapteeRealize();
TwoWayTarget *target = new TwoWayAdapter(adaptee);
target->request();
cout << "------------------------------------" << endl;
cout << "适配者通过双向适配器访问目标:";
target = new TargetRealize();
adaptee = new TwoWayAdapter(target);
adaptee->specificRequest();
delete target;
}
};
void main()
{
TwoWayAdapterTest *c = new TwoWayAdapterTest();
c->main();
delete c;
}
3、桥接模式
3.1 定义
设想如果要回值矩形、圆形、椭圆、正方形需要4个形状类,但是如果绘制的图形的需要不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
- 第一种设计方案是为每一种形状都提供一套颜色的版本。
- 第二种设计方案是根据司机需要对形状和颜色进行组合
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低类与类之间的耦合,减少了代码编写量。
桥接模式:将抽象部分与它的实现部分分离,是他们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式或者接口模式。
3.2 模式结构
- 抽象化角色:定义抽象类,并包含一个对实现化对象地引用。
- 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化角色:定义实现化角色的接口,供扩展抽象化角色角色调用。
- 具体实现化角色:给出实现化角色接口的具体实现。
3.3 C++代码
#include <iostream>
#include <string>
using namespace std;
//实现化角色
class Implementor
{
public:
virtual void Operationalmpl() = 0;
};
//具体实现化角色A
class ConcreteImplementorA:public Implementor
{
public:
void Operationalmpl()
{
cout << "具体实现化(Concrete Implementor)角色A被访问";
}
};
//具体实现化角色B
class ConcreteImplementorB :public Implementor
{
public:
void Operationalmpl()
{
cout << "具体实现化(Concrete Implementor)角色B被访问";
}
};
//抽象化角色
class Abstraction
{
protected:
Implementor *imple;
Abstraction(Implementor *imple)
{
this->imple = imple;
}
public:
virtual void Operation() = 0;
Abstraction() {}
};
//扩展抽象化角色
class RefinedAbstraction:public Abstraction
{
public:
RefinedAbstraction(Implementor *imple)
{
this->imple = imple;
}
void Operation()
{
cout << "扩展抽象化(Refined Abstraction)角色被访问" << endl;
this->imple->Operationalmpl();
}
};
class BridgeTest
{
public:
static void main()
{
Implementor *imple = new ConcreteImplementorA();
Abstraction *abs = new RefinedAbstraction(imple);
abs->Operation();
delete imple, abs;
}
};
int main()
{
BridgeTest *bt = new BridgeTest();
bt->main();
delete bt;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Color
{
public:
virtual string getColor() = 0;
};
class Yellow :public Color
{
public:
string getColor()
{
return "Yellow";
}
};
class Red :public Color
{
public:
string getColor()
{
return "Red";
}
};
class Bag
{
protected:
Color *color;
public:
void setColor(Color* color)
{
this->color = color;
}
virtual string getName() = 0;
};
class HandBag:public Bag
{
public:
string getName()
{
return color->getColor() + " HandBag";
}
};
class Wallet :public Bag
{
public:
string getName()
{
return color->getColor() + " Wallet";
}
};
class BagManager
{
public:
static void main()
{
Color* color = new Yellow();
Bag *bag = new Wallet();
bag->setColor(color);
cout << bag->getName() << endl;
delete color, bag;
}
};
int main()
{
BagManager *bm = new BagManager();
bm->main();
delete bm;
return 0;
}
3.4 模式分析
手机品牌和软件是两个概念,不同的软件可以在不同的手机上,不同的手机可以有相同的软件,两者都具有很大的变动性。如果我们单独以手机品牌或手机软件为基类来进行继承扩展的话,无疑会使类的数目剧增并且耦合性很高,(如果更改品牌或增加软件都会增加很多的变动)两种方式的结构如下:
所以将两者抽象出来两个基类分别是PhoneBrand和PhoneSoft,那么在品牌类中聚合一个软件对象的基类将解决软件和手机扩展混乱的问题,这样两者的扩展就相对灵活,剪短了两者的必要联系,结构图如下:
脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。
桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
3.5 优点
- 多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,交接模式是比多继承方案更好的解决方法。
- 交接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
3.6 缺点
- 桥接模式的引入会增加系统的理解和设计难度。由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
3.7 适用范围
- 一个类中存在两个(或者多个)独立变化的维度,而且这两个(或者多个)维度都需要独立地进行扩展。
- 不希望使用继承或者因为多层继承而导致系统中类的个数急剧增加。
- 一个系统需要在抽象类和具体类之间增加更多的灵活性,避免在两个层次之间建立静态继承关系,通过桥接可以使它们在抽象层建立一个关联关系。
3.7 扩展
有时桥接模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来。
4、装饰模式
4.1 定义
在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等。在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
一般有两种方式可以实现给一个类或对象增加行为:
- 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
- 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。
装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。
装饰模式:动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以成为包装器,与适配器模式的别名相同,但她们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
4.2 模式结构
- 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件添加附加的责任。
4.3 C++代码
#include <iostream>
#include <string>
using namespace std;
//抽象构件角色
class Component
{
public:
virtual void operation()=0;
};
//具体构件角色
class ConcreteComponent:public Component
{
public:
ConcreteComponent()
{
cout << "创建具体构件角色" << endl;
}
void operation()
{
cout << "调用具体构件角色的方法operation()" << endl;
}
};
//抽象装饰角色
class Decorator :public Component
{
protected:
Component *component;
public:
Decorator() {}
Decorator(Component *component)
{
this->component = component;
}
void operation()
{
component->operation();
}
};
//具体装饰角色
class ConcreteDecorator :public Decorator
{
public:
ConcreteDecorator(Component *component)
{
Decorator::component = component;
}
void operation()
{
Decorator::operation();
addedFunction();
}
void addedFunction()
{
cout << "为具体构件角色增加额外的功能addedFunction()" << endl;
}
};
class DecoratePattern
{
public:
static void main()
{
Component *p = new ConcreteComponent();
p->operation();
cout << "---------------------------------" << endl;
Component *d = new ConcreteDecorator(p);
d->operation();
delete p, d;
}
};
int main()
{
DecoratePattern* p = new DecoratePattern();
p->main();
delete p;
return 0;
}
4.4 优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,在使用时再对其进行组合,原有的代码无需改变,符合“开闭原则”。
4.5 缺点
- 使用装饰模式进行系统设计时,将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更灵活机动地特性,也同时意味着装饰模式比继承更容易出错,排错也很困难,对于多次装饰地对象,调试时寻找错误可能需要逐级排查,较为繁琐。
4.6 适用环境
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).
4.7 扩展
- 如果只有一个具体构件,而没有抽象构件时,可以让抽象装饰继承具体构件。
- 如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并