4.C++设计模式-结构型模型
让类和类进行组合,获得更大的结构。
4.1 代理模式
4.1.1 代理模式的理论
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
举例:
比如这里面服务器B的test可能还没写好,A访问不了,先访问代理类,写好了代替再代替访问
4.1.2 代理模式的案例
传统方法局限:是个人都能调用
#include <iostream>
using namespace std;
//提供一种代理来控制对其他对象的访问
class AbstraactCommonInterface {
public:
virtual void run() = 0;
};
//我已经写好的系统
class Mysystem {
public:
virtual void run() {
cout << "系统启动..." << endl;
}
};
//必须有权限验证,不是所有人都能来启动我的启动,必须提供用户名和密码
int main() {
//这样不行。是个人都能启动
Mysystem* system = new Mysystem;
system -> run();
return 0;
}
代理模式
优点:必须有权限验证,不是所有人都能来启动我的启动,必须提供用户名和密码
#include <iostream>
using namespace std;
//提供一种代理来控制对其他对象的访问
class AbstraactCommonInterface {
public:
virtual void run() = 0;
};
//我已经写好的系统
class Mysystem :public AbstraactCommonInterface{
public:
virtual void run() {
cout << "系统启动..." << endl;
}
};
//必须有权限验证,不是所有人都能来启动我的启动,必须提供用户名和密码
class MysystemProxy :public AbstraactCommonInterface {
public:
MysystemProxy(string username,string password) {
this->mUsername = username;
this->mPassword = mPassword;
}
bool checkUsernameAndPassword() {
if (mUsername == "admin"&&mPassword == "admin") {
return true;
}
return false;
}
virtual void run() {
if (checkUsernameAndPassword()) {
cout << "用户名和密码正确,验证通过..." << endl;
this->pSystem->run();
}
else {
cout << "用户名或密码错误,权限不足...." << endl;
}
}
~MysystemProxy() {
if (pSystem != NULL) {
delete pSystem;
}
}
public:
Mysystem* pSystem;
string mUsername;
string mPassword;
};
void test01() {
MysystemProxy* proxy = new MysystemProxy("root","admin");
proxy->run();
}
int main() {
#if 0
//这样不行。是个人都能启动
Mysystem* system = new Mysystem;
system -> run();
#endif
//调用代理模式
test01();
return 0;
}
4.1.3 代理模式的举例
(1)上外网
(2)缓冲服务器
4.1.4 代理模式的优缺点
优点:
(1)能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2)客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
缺点:
代理实现较为复杂。
4.1.5 代理模式的适用场景
为其他对象提供一种代理以控制对这个对象的访问。
4.2 装饰模式
4.2.1 装饰模式的理论
装饰模式又叫包装模式,通过一种对客户端透明的方式来扩展对象功能,是继承关系的一种替代。
装饰模式就是把要附加的功能分别放在单独的类中,并让这个类包含它要装饰的对象,当需要执行时,客户端就可以有选择的、按顺序的使用装饰功能包装对象。
4.2.2 装饰模式的案例
#include <iostream>
using namespace std;
//一般情况下,用继承实现类的功能拓展
//装饰模式 可以动态给一个类增加功能
//抽象英雄
class AbstractHero {
public:
virtual void ShowStatus() = 0;
public:
int mHp;
int mMp;
int mAt;
int mDf;
};
//具体英雄
class HeroA :public AbstractHero {
public:
HeroA() {
mHp = 0;
mMp = 0;
mAt = 0;
mDf = 0;
}
virtual void ShowStatus() {
cout << "血量:" << mHp << endl;
cout << "魔法:" << mMp << endl;
cout << "攻击:" << mAt << endl;
cout << "防御:" << mDf << endl;
}
};
//英雄穿上某个装饰物 那么他还是个英雄
//装饰物
class AbstractEquipmet : public AbstractHero {
public:
AbstractEquipmet(AbstractHero* hero) {
this->pHero = hero;
}
virtual void ShowStatus() = 0;
public:
AbstractHero* pHero;
};
//狂徒
class KuangtuEquipment :public AbstractEquipmet {
public:
KuangtuEquipment(AbstractHero* hero) :AbstractEquipmet(hero) {}
//增加额外的功能
void AddKuangtu() {
cout << "英雄穿上狂徒之后..." << endl;
this->mHp = this->pHero->mHp;
this->mMp = this->pHero->mMp;
this->mAt = this->pHero->mAt;
this->mDf = this->pHero->mDf + 30;
delete this->pHero;
}
virtual void ShowStatus() {
AddKuangtu();
cout << "血量:" << mHp << endl;
cout << "魔法:" << mMp << endl;
cout << "攻击:" << mAt << endl;
cout << "防御:" << mDf << endl;
}
};
//无尽
class Wujing : public AbstractEquipmet {
public:
Wujing(AbstractHero* hero) :AbstractEquipmet(hero) {}
//增加额外的功能
void AddWujing() {
cout << "英雄穿上无尽之后..." << endl;
this->mHp = this->pHero->mHp;
this->mMp = this->pHero->mMp;
this->mAt = this->pHero->mAt + 80;
this->mDf = this->pHero->mDf;
delete this->pHero;
}
virtual void ShowStatus() {
AddWujing();
cout << "血量:" << mHp << endl;
cout << "魔法:" << mMp << endl;
cout << "攻击:" << mAt << endl;
cout << "防御:" << mDf << endl;
}
};
void test01() {
AbstractHero* hero = new HeroA;
hero->ShowStatus();
cout << "----------------------------" << endl;
//给裸奔的英雄穿上衣服后
hero = new KuangtuEquipment(hero);
hero->ShowStatus();
cout << "----------------------------" << endl;
//装备武器
hero = new Wujing(hero);
hero->ShowStatus();
}
int main() {
test01();
return 0;
}
4.2.3 装饰模式的优缺点
优点:
(1)对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
(2)可以通过一种动态的方式来扩展一个对象的功能,从而实现不同的行为。
(3)可以对一个对象进行多次装饰。
(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
缺点:
使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,影响程序的性能。
4.2.4 装饰模式的适用场景
(1) 动态、透明的方式给单个对象添加职责。
(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
4.3 外观模式
4.3.1 外观模式理论
废话理论(建议直接看总结):
根据迪米特法则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。
Facade模式也叫外观模式,是由GoF提出的23种设计模式中的一种。Facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称作facade。
比如有两个子系统,如果都初始化,传统需要一个一个new一下,现在提供一个类,在这个类里面做完初始化工作。
**Façade(外观角色):**为调用方, 定义简单的调用接口。
**SubSystem(子系统角色):**功能提供者。指提供功能的类群(模块或子系统)
总结:外观模式就是将复杂的子类系统抽象到同一个的接口进行管理,外界只需要通过此接口与子类系统进行交互,而不必要直接与复杂的子类系统进行交互。
4.3.2 外观模式的案例
根据类图,实现家庭影院外观模式应用。
实现KTV模式:电视打开,灯关掉,音响打开,麦克风打开,dvd打开;
实现游戏模式:电视打开,音响打开,游戏机打开。
#include <bits/stdc++.h>
using namespace std;
//电视机
class Televison {
public:
void On() {
cout << "电视机打开..." << endl;
}
void Off() {
cout << "电视机关闭" << endl;
}
};
//灯
class Light {
public:
void On() {
cout << "灯打开..." << endl;
}
void Off() {
cout << "灯关闭" << endl;
}
};
//音箱
class Audio {
public:
void On() {
cout << "音箱打开..." << endl;
}
void Off() {
cout << "音箱关闭" << endl;
}
};
//麦克风
class Microphone {
public:
void On() {
cout << "麦克风打开..." << endl;
}
void Off() {
cout << "麦克风关闭" << endl;
}
};
//DVD
class DVDPlayer {
public:
void On() {
cout << "DVD播放器打开..." << endl;
}
void Off() {
cout << "DVD播放器关闭" << endl;
}
};
//游戏机
class Gamemachine {
public:
void On() {
cout << "游戏机打开..." << endl;
}
void Off() {
cout << "游戏机关闭" << endl;
}
};
//KTV模式
class KTVModel {
public:
KTVModel() {
pTv = new Televison;
pLight = new Light;
pAudio = new Audio;
pMicrophone = new Microphone;
pDVD = new DVDPlayer;
}
void OnKtv() {
pTv->On();
pLight->Off();
pAudio->On();
pMicrophone->On();
pDVD->On();
}
void OffKtv() {
pTv->Off();
pLight->On();
pAudio->Off();
pMicrophone->Off();
pDVD->Off();
}
~KTVModel() {
delete pTv;
delete pLight;
delete pAudio;
delete pMicrophone;
delete pDVD;
}
public:
Televison* pTv;
Light* pLight;
Audio* pAudio;
Microphone* pMicrophone;
DVDPlayer* pDVD;
};
void test01() {
KTVModel* ktv = new KTVModel;
ktv->OnKtv();
}
int main() {
test01();
system("pause");
return 0;
}
4.3.3 外观模式的优缺点
优点:
(1)它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
(2)它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
(3)一个子系统的修改对其他子系统没有任何影响。
缺点:
(1)不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
(2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
4.3.4 外观模式的适用场景
(1) 复杂系统需要简单入口使用。
(2) 客户端程序与多个子系统之间存在很大的依赖性。
(3) 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
4.4 适配器模式
4.4.1 适配器模式理论
将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
4.4.2 适配器模式案例
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//适配器模式 就是将已经写好的接口,但是这个接口不符合需求
//将写好的接口转换成目标接口
//这函数我已经写好
struct Myprint{
void operator()(int v1,int v2) {
cout << v1 + v2 << endl;
}
};
//定义目标接口 我要是配偶 适配成什么样的,
//要适配成只能传一个参数的,适配for_each第三个参数的适用
class Target {
public:
virtual void operator()(int v) = 0;
};
//写适配器
class Adapater :public Target {
public:
Adapater(int param) {
this->param = param;
}
virtual void operator() (int v) {
print(v,param);
}
public:
Myprint print;
int param;
};
//MyBind2nd,原来param固定的10,现在提供一个方法改
Adapater MyBind2nd(int v) {
return Adapater(v);
}
int main(void) {
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
//适配器模式的运用
//for_each()的第三个参数是个带一个参数的函数,但是Myprint需要两个参数
for_each(v.begin(),v.end(), MyBind2nd(10));
return 0;
}
4.2.3 适配器模式的优缺点
优点:
(1)将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
(2)增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3)灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点:
适配器中置换适配者类的某些方法比较麻烦。
4.2.4 适配器模式的适用场景
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。