0 结构型模式
让类和类进行组合,获得更大的结构,获得新功能的方式。
1 代理模式
Proxy 模式又被叫做代理模式,是结构型的设计模式之一,它可以 为其他对象提供一种代理以控制对这个对象的访问。
所谓 代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须 通过代理与被代理的目标类交互,而代理一般在交互的过程中,进行某些特别的处理。
1.1 模式中的角色和职责
subject(抽象主题角色):真实主题与代理主题的共同接口。
RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
1.2 案例
示例代码:
#include <iostream>
#include <string>
using namespace std;
// 物品
class Item
{
public:
Item(string kind,bool fake)
{
this->kind = kind;
this->fake = fake;
}
// 物品类别
string kind;
// true:假货,false:真货
bool fake;
};
// subject(抽象主题角色),抽象的购物方式,具有买的功能
class Shopping
{
public:
virtual void buy(Item& item) = 0;
};
// RealSubject(真实主题角色),去韩国购物
class KoreaShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去韩国购物,买了" << item.kind << endl;
}
};
// RealSubject(真实主题角色),去美国购物
class USAShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去美国购物,买了" << item.kind << endl;
}
};
// RealSubject(真实主题角色),去非洲购物
class AfricaShopping : public Shopping
{
public:
virtual void buy(Item& item)
{
cout << "去非洲购物,买了" << item.kind << endl;
}
};
// Proxy(代理主题角色),海外代购 代理,实现了购物模式,而且还增加了辨别货物真伪和海关安检具体业务。
class OverseasProxy : public Shopping
{
public:
OverseasProxy(Shopping* shopping)
{
this->shopping = shopping;
}
~OverseasProxy()
{
delete this->shopping;
}
bool distinguish(Item& item)
{
cout << "对物品[" << item.kind << "]辨别真伪." << endl;
return item.fake;
}
void check(Item& item)
{
cout << "通过海关安检,带回国内" << endl;
}
virtual void buy(Item& item)
{
if(distinguish(item) == false)
{
this->shopping->buy(item);
check(item);
}
else
{
cout << "发现伪货[" << item.kind << "],不能购买" << endl;
}
}
private:
Shopping* shopping;
};
int main(void)
{
Item item1("化妆品",false);
Item item2("学位证",true);
Item item3("空调",false);
Shopping* shopping = NULL;
OverseasProxy* proxy = NULL;
// 1.去韩国买化妆品
proxy = new OverseasProxy(new KoreaShopping);
proxy->buy(item1);
delete proxy;
// 2.去美国买学位证
proxy = new OverseasProxy(new USAShopping);
proxy->buy(item2);
delete proxy;
// 3.去非洲买空调
proxy = new OverseasProxy(new AfricaShopping);
proxy->buy(item3);
delete proxy;
return 0;
}
运行结果:
1.3 代理模式的种类
(1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)
(2)虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
(3)保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
(4)缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
(5)智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
1.4 优缺点
优点:
(1)能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2)客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
(1)代理实现较为复杂
1.5 适用场景
为其他对象提供一种代理以控制对这个对象的访问。
2 装饰模式
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式 比生成子类实现更为灵活。
2.1 模式中的角色和职责
Component(抽象构件):它是 具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
2.2 案例
示例代码:
#include <iostream>
#include <string>
using namespace std;
// Component(抽象构件),它是具体构件和抽象装饰类的共同父类,手机
class Phone
{
public:
virtual void show() = 0;
};
// ConcreateComponent(具体构件),iphone
class Iphone : public Phone
{
public:
Iphone(string kind)
{
this->kind = kind;
}
virtual void show()
{
cout << "我掏出了 iphone-" << kind << "秀了秀" << endl;
}
private:
string kind;
};
// ConcreateComponent(具体构件),小米手机
class MiPhone : public Phone
{
public:
MiPhone(string kind)
{
this->kind = kind;
}
virtual void show()
{
cout << "我掏出了 小米-" << kind << "秀了秀" << endl;
}
private:
string kind;
};
// Decorator(抽象装饰类),手机装饰器
class DecoratorPhone : public Phone
{
public:
DecoratorPhone()
{
}
DecoratorPhone(Phone* phone)
{
this->phone = phone;
}
virtual void show()
{
this->phone->show();
}
private:
Phone* phone;
};
// ConcreteDecorator(具体装饰类),贴膜装饰器
class DecoratorPhoneMo : public DecoratorPhone
{
public:
DecoratorPhoneMo(Phone* phone)
{
this->phone = phone;
}
void AddMo()
{
cout << "装饰:手机贴膜" << endl;
}
virtual void show()
{
this->phone->show();
AddMo();
}
private:
Phone* phone;
};
// ConcreteDecorator(具体装饰类),皮套装饰器
class DecoratorPhoneTao : public DecoratorPhone
{
public:
DecoratorPhoneTao(Phone* phone)
{
this->phone = phone;
}
void AddTao()
{
cout << "装饰:手机外套" << endl;
}
virtual void show()
{
this->phone->show();
AddTao();
}
private:
Phone* phone;
};
int main(void)
{
Phone *phone = NULL;
DecoratorPhone* hasMoPhone = NULL;
DecoratorPhone* hasTaoPhone = NULL;
DecoratorPhone* hasMoTaoPhone = NULL;
// 定义一个 iphone12
phone = new Iphone("12");
// 给 iphone12 贴膜
hasMoPhone = new DecoratorPhoneMo(phone);
// 给 iphone12 加上皮套
hasTaoPhone = new DecoratorPhoneTao(phone);
hasMoPhone->show();
hasTaoPhone->show();
// 给有皮套的 iphone12 再贴膜
hasMoTaoPhone = new DecoratorPhoneMo(hasTaoPhone);
hasMoTaoPhone->show();
delete hasMoPhone;
delete hasTaoPhone;
delete hasMoTaoPhone;
delete phone;
return 0;
}
运行结果:
2.3 优缺点
(1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
(2)可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。
(3) 可以对一个对象进行多次装饰。
(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
缺点:
(1) 使用装饰模式进行系统设计时 将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,影响程序的性能。
2.4 适用场景
(1)动态、透明的方式给单个对象添加职责。
(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展
和维护时可以使用装饰模式。
3 外观模式
外观模式即 Facade 模式,是由 GoF 提出的 23 种设计模式中的一种。Facade 模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称为 Facade 。
根据迪米特法则,如果两个类不直接通信,那么这两个类就不应该发生直接的相互作用。
外观模式就是将复杂的子类系统(SubSystem)抽象到同一个接口(Façade)进行管理,外界只需要通过此接口(Façade)与子类系统(SubSystem)进行交互,而不需要直接与复杂的子类系统(SubSystem)进行交互。
3.1 模式中的角色和职责
Façade(外观角色):为调用方 提供简单的调用接口。
SubSystem(子系统角色):功能提供者。指提供功能的类群(模块或子系
统)。
3.2 案例
示例:
根据类图,实现家庭影院外观模式应用。
实现KTV模式:电视打开,灯关掉,音响打开,麦克风打开,dvd打开。
实现游戏模式:电视打开,音响打开,游戏机打开。
示例代码:
#include <iostream>
using namespace std;
class TV
{
public:
void On()
{
cout << "电视打开了" << endl;
}
void Off()
{
cout << "电视关闭了" << endl;
}
};
class DVD
{
public:
void On()
{
cout << "DVD打开了" << endl;
}
void Off()
{
cout << "DVD关闭了" << endl;
}
};
class Xbox
{
public:
void On()
{
cout << "Xbox打开了" << endl;
}
void Off()
{
cout << "Xbox关闭了" << endl;
}
};
class MikePhone
{
public:
void On()
{
cout << "MikePhone打开了" << endl;
}
void Off()
{
cout << "MikePhone关闭了" << endl;
}
};
class Light
{
public:
void On()
{
cout << "Light打开了" << endl;
}
void Off()
{
cout << "Light关闭了" << endl;
}
};
class HomePlayer
{
public:
//ktv模式的接口
void doKTV()
{
light.Off();
tv.On();
dvd.On();
}
//游戏模式的接口
void doGame()
{
tv.On();
xbox.On();
}
Light light;
TV tv;
MikePhone mike;
Xbox xbox;
DVD dvd;
};
int main(void)
{
HomePlayer hp;
cout << "进入ktv模式" << endl;
hp.doKTV();
cout << "进入游戏模式" << endl;
hp.doGame();
return 0;
}
运行结果:
3.3 优缺点
优点:
(1)它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并
使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简
**单,与之关联的对象也很少。 **
(2)它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会
**影响到调用它的客户端,只需要调整外观类即可。 **
(3)一个子系统的修改对其他子系统没有任何影响。
缺点:
(1)不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类
做太多的限制则减少了可变性和灵活性。
(2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了
开闭原则。
3.4 适用场景
(1)复杂系统需要简单入口使用。
(2)客户端程序与多个子系统之间存在很大的依赖性。
(3)在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与
层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度
4 适配器模式
适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另外一个接口。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
4.1 模式中的角色和职责
Target(目标抽象类):目标抽象类 定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adaptee(适配者类):适配者即被适配的角色,它 定义了一个已经存在的接口,这个接口需要适配,适配者类 一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,在对象适配器中,它 **通过继承 Target 并关联一个 Adaptee 对象使二者产生联系。 **
4.2 案例
示例代码:
#include <iostream>
using namespace std;
// Target(目标抽象类),5V 电压
class V5
{
public:
virtual void useV5() = 0;
};
// Adaptee(适配者类),220V 电压
class V220
{
public:
virtual void useV220()
{
cout << "用 220V 电压进行充电" << endl;
}
};
// Adapter(适配器类),手机充电器
class PhoneChargeAdapter : public V5
{
public:
virtual void useV5()
{
cout << "手机充电器对电压进行适配" << endl;
m_v220.useV220();
}
private:
V220 m_v220;
};
class Phone
{
public:
Phone()
{
v5 = new PhoneChargeAdapter;
}
~Phone()
{
if(v5 != NULL)
delete v5;
}
void charge()
{
cout << "手机进行充电" << endl;
v5->useV5();
}
private:
V5* v5;
};
int main(void)
{
Phone iphone;
iphone.charge();
return 0;
}
运行结果:
4.3 优缺点
优点:
(1)将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者
类,无须修改原有结构。
(2)增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类
中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者
类可以在多个不同的系统中复用。
(3)灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改
原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
适配器中置换适配者类的某些方法比较麻烦
4.4 适用场景
(1)系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系
统的需要,甚至没有这些类的源代码。
(2)想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的
一些类,包括一些可能在将来引进的类一起工作