设计模式的目的:增强可维护性和可复用性,在一定环境下,用固定的套路解决问题,无关语言
面向对象原则目的:高内聚低耦合
注意:因为设计模式讲究可复用性,所以相关代码都会比较长。
面向对象七大原则:
--单一职责,接口隔离,里氏替换,依赖倒置,迪米特(最少知识),开闭,合成复用
一、单一职责原则
- 一个类只负责一项职责,假设类1负责A,B两个功能,修改A功能时发生错误会导致B也出错。应建立类1负责A功能,类2负责B功能。
二、接口隔离原则
- 客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小接口上
先给一个不符合此原则的例子:
#include <iostream>
using namespace std;
class Base{
public:
Base(){}
virtual ~Base(){}
public:
virtual void function1()=0;
virtual void function2()=0;
virtual void function3()=0;
virtual void function4()=0;
virtual void function5()=0;
};
class A:public Base{
public:
virtual void function1(){cout<<"A1"<<endl;}
virtual void function2(){cout<<"A2"<<endl;}
virtual void function3(){cout<<"A3"<<endl;}
virtual void function4(){}
virtual void function5(){}
};
class B:public Base{
public:
virtual void function1(){cout<<"B1"<<endl;}
virtual void function2(){cout<<"B2"<<endl;}
virtual void function3(){}
virtual void function4(){}
virtual void function5(){cout<<"B5"<<endl;}
};
void run(){
A *a = new A();
a->function1();
delete a;
B *b = new B();
b->function5();
delete b;
}
int main(){
run();
return 0;
}
能够发现如果Base类中的接口非常多的话,子类之中会继承很多不需要的方法。所以需要把Base类拆分成几个类别让子类分别继承接口。
class Base1{
public:
virtual void function1()=0;
virtual void function2()=0;
};
class Base2{
virtual void function3()=0;
};
class Base3{
virtual void function5()=0;
};
class A:public Base1,public Base2{
public:
virtual void function1(){cout<<"A1"<<endl;}
virtual void function2(){cout<<"A2"<<endl;}
virtual void function3(){cout<<"A3"<<endl;}
};
class B:public Base1,public Base3{
public:
virtual void function1(){cout<<"B1"<<endl;}
virtual void function2(){cout<<"B2"<<endl;}
virtual void function5(){cout<<"B5"<<endl;}
};
三、里氏替换原则
- 只要父类能实现的子类就应该可以实现;或者说,任何抽象类出现的地方都可以用他的实现类进行替换
举个例子:企鹅属于鸟但不会飞,所以我们并不能在鸟类中添加飞行的方法,或者说企鹅并不能被定义成鸟类的子类。否则企鹅无法代替父类实现飞行方法。
四、依赖倒置
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象
这块听起来确实抽象,举个例子就能明白了
传统业务逻辑:高层模块->中层模块->底层模块
传统的设计模式通常自顶向下逐级依赖,每个模块之间耦合度极高,若任意修改其中一个,很容易导致全面积的修改,维护难度很大,所以依赖倒置利用多态的先天特性,对中间抽象层进行依赖,底层和高层之间进行了解耦合。
也就是:高级模块->抽象层<-实现层
传统业务逻辑:
#include <iostream>
using namespace std;
class BankWorker{
public:
void payService(){cout<<"pay"<<endl;}
void transferService(){cout<<"transfer"<<endl;}
void saveService(){cout<<"save"<<endl;}
};
void run(){
BankWorker* worker = new BankWorker();
worker->payService();
worker->transferService();
worker->saveService();
}
int main(){
run();
return 0;
}
弊端:如果想新增业务还需要更改类中的代码,在业务复杂场景下,为了增加一个新功能修改源代码肯定是不太合适的。
//抽象层
class AbstractWorkerBank{
public:
virtual void doBussiness()=0;
};
//只处理pay
class PayBankWorker:public AbstractWorkerBank{
public:
virtual void doBussiness(){cout<<"Pay"<<endl;}
};
class TransferBankWorker:public AbstractWorkerBank{
public:
virtual void doBussiness(){cout<<"Transfer"<<endl;}
};
class SaveBankWorker:public AbstractWorkerBank{
public:
virtual void doBussiness(){cout<<"Save"<<endl;}
};
//高层
void DoBankBussiness(AbstractWorkerBank* work){
work->doBussiness();
delete work;
}
void run(){
DoBankBussiness(new PayBankWorker);
DoBankBussiness(new TransferBankWorker);
DoBankBussiness(new SaveBankWorker);
}
五、迪米特法则(最少知识法则)
- 一个对象应该对其它对象保持最少的了解
例子:租客想要租房子,周围有好个房源,但如果想要了解需要一个个进行遍历查看所有属性。符合迪米特法则的场景是,增加中介,租客给需求,中介负责处理,这样就不需要每来一个租客都进行重复的查看。
六、开闭原则
- 一个软件实体,如类、模块、函数应该对外开放扩展,对内禁止修改
可以参考第四点银行办理业务的例子,减少对源代码的修改,通过添加扩展达到相同的功能
七、合成复用原则
- 如果使用继承,父类的任何修改都会影响到子类,而组合就降低了依赖关系程度
先给个继承例子,存在A车和B车,Person对象想要开车:
#include <iostream>
using namespace std;
//抽象车
class AbstractCar{
public:
virtual void run()=0;
};
//A型车
class ACar:public AbstractCar{
public:
virtual void run(){cout<<"ACar"<<endl;}
};
//B型车
class BCar:public AbstractCar{
public:
virtual void run(){cout<<"BCar"<<endl;}
};
class Person:public ACar{
public:
void drive(){run();}
};
void run(){
Person *person = new Person();
person->drive();
delete person;
}
int main(){
run();
return 0;
}
用继承的弊端就是无法同时拥有两辆车,下面看组合:
//class Person:ACar{
//public:
// void drive(){run();}
//};
class Person{
public:
Person(AbstractCar* car){
mycar = car;
}
void drive(){mycar->run();}
private:
AbstractCar* mycar;
};
void run(){
Person *person = new Person(new BCar);
person->drive();
delete person;
person = new Person(new ACar);
person->drive();
delete person;
}
以上就是七大原则,接下来我们看看常用的设计模式。
设计模式
说的最多的是23种设计模式,但还有一种简单工厂模式也较为常用,这里只介绍最常用的12种设计模式,设计模式中又分为三类:
1、创建型模式(5+1)
单例,简单工厂(不符合面向对象原则,不在GoF专家组提出的23种设计模式中,但在合适的场景下常用),工厂方法,抽象工厂,原型,建造者
2、结构型模式(7)
适配器,装饰,外观,代理,桥接,组合,享元
3、行为型模式(11)
观察者,策略,命令,模板方法,职责链,解释器,迭代器,中介者,备忘录,状态,访问者
创建型模式
(1)简单工厂模式
简单工厂模式并不属于GoF的23种设计模式,因为工厂类集中了所有实例的创建,违反了高内聚责任分配原则(单一功能原则),且添加新类时需要修改工程类,违反了开闭原则。对维护和扩展非常不利,适用于创建的对象类别很少,客户只知道传入参数,对创建对象逻辑不关心的场景。一般只在很简单的情况下应用。
#include<iostream>
#include<string>
using namespace std;
typedef enum ProductTypeTag
{
apple,
banana,
pear
}PRODUCTTYPE;
//抽象水果
class Fruit{
public:
virtual void showName() = 0;
};
class Apple : public Fruit{
public:
virtual void showName(){cout << "Apple" << endl;}
};
class Banana : public Fruit{
public:
virtual void showName(){cout << "Banana" << endl;}
};
class Pear : public Fruit{
public:
virtual void showName(){cout << "Pear" << endl;}
};
//水果工厂
class FruitFactory{
public:
static Fruit* CreateFruit(PRODUCTTYPE type){
switch(type){
case apple:
return new Apple;
case banana:
return new Banana;
case pear:
return new Pear;
default:
return NULL;
}
}
};
void run(){
Fruit* fruit = NULL;
fruit = FruitFactory::CreateFruit(apple); //工厂生产苹果
fruit->showName();
delete fruit;
fruit = FruitFactory::CreateFruit(banana); //工厂生产香蕉
fruit->showName();
delete fruit;
fruit = FruitFactory::CreateFruit(pear); //工厂生产鸭梨
fruit->showName();
delete fruit;
}
int main(){
run();
return 0;
}
优点:
(1)工厂存在必要的判断逻辑,可以决定何时创建产品实例,客户无需创建,直接消费即可
(2)简单工厂实现了对责任的分割,专门用于创建对象
(3)客户无需知道产品的类名,Apple的类型改为奇怪的名字也无所谓,传入apple参数即可
缺点:
(1)FruitFactory类职责过重,各种水果都在一个类中实现,违反了单一职责原则,一旦某一部分出错,整个系统都会崩溃
(2)当需要添加新类型的水果时,需要修改源代码,违反了开闭原则。
(3)每增加一类水果都需要增加类的个数
(4)简单工厂使用了静态工厂,工厂角色无法基于继承结构
适用场景:
(1)工厂类负责创建的对象较少,不会造成工厂中业务逻辑太繁杂
(2)客户只知道参数,无需关心对象的生产过程
(2)工厂方法模式
工厂方法模式时简单工厂模式的衍生,符合开闭原则,简单来说工厂方法=简单工厂+开闭原则。
定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中(简单工厂创建过程全部都在在父类实现)。核心工程类不再负责产品的创建,核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,进一步的抽象化,实现了可扩展性。
#include<iostream>
using namespace std;
//抽象水果
class AbstractFruit{
public:
virtual void showName() = 0;
};
/* 具体水果 start */
//苹果
class Apple : public AbstractFruit{
public:
virtual void showName(){cout << "Apple" << endl;}
};
//香蕉
class Banana : public AbstractFruit{
public:
virtual void showName(){cout << "Banana" << endl;}
};
//梨
class Pear : public AbstractFruit{
public:
virtual void showName(){cout << "Pear" << endl;}
};
/* 具体水果 end */
//抽象工厂
class AbstractFactory{
public:
virtual AbstractFruit* CreateFruit() = 0;
};
/* 具体工厂类 start */
//苹果工厂
class AppleFactory : public AbstractFactory{
public:
virtual AbstractFruit* CreateFruit(){return new Apple;}
};
//香蕉工厂
class BananaFactory : public AbstractFactory{
public:
virtual AbstractFruit* CreateFruit(){return new Banana;}
};
//梨工厂
class PearFactory : public AbstractFactory{
public:
virtual AbstractFruit* CreateFruit(){return new Pear;}
};
/* 具体工厂类 end */
//测试
void test01(){
AbstractFactory* factory = NULL;
AbstractFruit* fruit = NULL;
factory = new AppleFactory; //创建苹果工厂
fruit = factory->CreateFruit(); //苹果工厂生产苹果
fruit->showName();
delete factory;
delete fruit;
factory = new BananaFactory; //创建香蕉工厂
fruit = factory->CreateFruit(); //香蕉工厂生产苹果
fruit->showName();
delete factory;
delete fruit;
factory = new PearFactory; //创建梨工厂
fruit = factory->CreateFruit(); //梨工厂生产苹果
fruit->showName();
delete factory;
delete fruit;
}
int main(){
test01();
return 0;
}
优点:
(1)无需记住类名,甚至连参数都不需要,选择工厂调用生产即可
(2)实现了对象的创建和使用分离
(3)可扩展性高,无需修改接口和原类
缺点:
(1)类太多,抽象水果类,苹果类,香蕉类,梨类,抽象工厂类,再加三个对应的工厂类。。。
(2)系统较为抽象,较为繁琐,可读性不高
适用场景:
(1)客户端不知道它所需要的对象的类
(2)抽象工厂类通过其子类来指定创建哪个对象
(3)抽象工厂模式
工厂方法模式引入了工厂等级结构,解决了简单工厂中工厂职责太重的问题,但每个工厂只生产一类产品,可能存在大量的工厂类,增加了系统的开销。于是将产品组成产品族,由同一工厂同一生产。如果这里存在N个国家的M种水果,工厂方法模式会创建N*M个工厂类,而抽象工厂模式会有对应的N个工厂(M个工厂也可以),每个工厂可以创建对应国家的不同水果。
#include <iostream>
using namespace std;
//抽象苹果
class AbstractApple{
public:
virtual void ShowName() = 0;
};
//中国苹果
class ChinaApple : public AbstractApple{
public:
virtual void ShowName(){cout << "ChinaApple" << endl;}
};
//美国苹果
class USAApple : public AbstractApple{
public:
virtual void ShowName(){
cout << "USAApple" << endl;
}
};
//日本苹果
class JapanApple : public AbstractApple{
public:
virtual void ShowName(){cout << "JapanApple" << endl;}
};
//抽象香蕉
class AbsrtactBanana{
public:
virtual void ShowName() = 0;
};
//中国香蕉
class ChinaBanana : public AbsrtactBanana{
public:
virtual void ShowName(){cout << "ChinaBanana" << endl;}
};
//美国香蕉
class USABanana : public AbsrtactBanana{
public:
virtual void ShowName(){cout << "USABanana" << endl;}
};
//日本香蕉
class JapanBanana : public AbsrtactBanana{
public:
virtual void ShowName(){cout << "JapanBanana" << endl;}
};
//抽象梨
class AbstractPear{
public:
virtual void ShowName() = 0;
};
//中国梨
class ChinaPear : public AbstractPear{
public:
virtual void ShowName(){cout << "ChinaPear" << endl;}
};
//美国梨
class USAPear : public AbstractPear{
public:
virtual void ShowName(){cout << "USAPear" << endl;}
};
//日本梨
class JapanPear : public AbstractPear{
public:
virtual void ShowName(){cout << "JapanPear" << endl;}
};
//抽象工厂 针对产品族
class AbstracFactory{
public:
virtual AbstractApple* CreateApple() = 0;
virtual AbsrtactBanana* CreateBanana() = 0;
virtual AbstractPear* CreatePear() = 0;
};
//中国工厂
class ChinaFactory : public AbstracFactory{
public:
virtual AbstractApple* CreateApple(){
return new ChinaApple;
}
virtual AbsrtactBanana* CreateBanana(){
return new ChinaBanana;
}
virtual AbstractPear* CreatePear(){
return new ChinaPear;
}
};
//美国工厂
class USAFactory : public AbstracFactory{
public:
virtual AbstractApple* CreateApple(){
return new USAApple;
}
virtual AbsrtactBanana* CreateBanana(){
return new USABanana;
}
virtual AbstractPear* CreatePear(){
return new USAPear;
}
};
//日本工厂
class JapanFactory : public AbstracFactory{
public:
virtual AbstractApple* CreateApple(){
return new JapanApple;
}
virtual AbsrtactBanana* CreateBanana(){
return new JapanBanana;
}
virtual AbstractPear* CreatePear(){
return new JapanPear;
}
};
void test01(){
AbstracFactory* factory = NULL;
AbstractApple* apple = NULL;
AbsrtactBanana* banana = NULL;
AbstractPear* pear = NULL;
//中国工厂
factory = new ChinaFactory;
apple = factory->CreateApple();
banana = factory->CreateBanana();
pear = factory->CreatePear();
apple->ShowName();
banana->ShowName();
pear->ShowName();
delete pear;
delete banana;
delete apple;
delete factory;
}
int main(void){
test01();
return 0;
}
优点:
(1)拥有工厂方法模式的优点(无需记名字,创建使用分离,可扩展)
(2)增加新的产品族很方便,这里指的是新增一个国家的话,新增一个相应的国家工厂即可
缺点:
(1)如果增加新的产品等级结构很麻烦,这里指的是新增一个水果种类,那么要进行大幅度的修改非常不方便,违背了开闭原则。
适用场景:
(1)系统中有多于一个产品族,且需要动态改变,添加产品族
(2)产品等级结构稳定,设计完成后无需改动产品等级结构
(4)单例模式
单例模式很简单,一个类中只有一个实例,且易于外界访问。
如果构建单例:
(1)单例模式的类只提供私有的构造函数(防止外部对其实例化)
(2)类中含有一个该类的静态私有对象(唯一实例)
(3)该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象
单例分为懒汉式和饿汉式,懒汉式在第一次使用前才被创建,但在多线程情况下,其它线程需要调用的时候可能还未实例化,存在线程安全问题;饿汉式在系统运行的一开始就已经被创建,不存在线程安全问题
#include <iostream>
using namespace std;
//实现单例步骤
//1. 构造函数私有化
//2. 增加静态私有的当前类的指针变量
//3. 提供静态对外接口,可以让用户获得单例对象
class A{
private:
A(){cout<<"创建A"<<endl;}
public:
static A* getInstance(){
if(a == NULL)
a = new A();
return a;
}
private:
static A* a;
};
A* A::a = NULL;
//A* A::a = new A(); //饿汉式
void test01(){
A* p1 = A::getInstance();
A* p2 = A::getInstance();
if (p1 == p2)
cout << "两个指针指向同一块内存空间,是单例!" << endl;
else
cout << "不是单例模式!" << endl;
}
int main(void){
test01();
return 0;
}
优点:
(1)提供了对唯一实例的受控访问
(2)节约系统资源,只存在一个对象
缺点:
(1)扩展难,无抽象层
(2)单例类职责过重
适用场景:
(1)系统只需要一个实例对象,如任务管理器
(2)只允许使用一个公共访问节点,无法通过其它途径访问
结构型模式
让类与类进行组合
(1)代理模式
对其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,比如科学上网,而代理对象可以在客户端和目标对象之间起到中介的作用。玩家操控游戏角色也相当于这个模式。
此处用系统的权限举例,用户开启系统时需要通过中间件系统验证类,通过后验证类才会调用系统的启动方法。
#include <iostream>
using namespace std;
//提供一种代理来控制对其他对象的访问
class AbstractCommonInterface{
public:
virtual void run() = 0;
};
//系统
class MySystem : public AbstractCommonInterface{
public:
virtual void run(){cout << "系统启动..." << endl;}
};
//必须有要权限验证,不是所有人都能来启动我的启动,提供用户名和密码
class MySystemProxy : public AbstractCommonInterface{
public:
MySystemProxy(string username,string password){
this->mUsername = username;
this->mPassword = password;
pSystem = new MySystem;
}
bool checkUsernameAndPassoword(){
if (mUsername == "admin" && mPassword == "admin"){
return true;
}
return false;
}
virtual void run(){
if (checkUsernameAndPassoword()){
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("admin","admin");
proxy->run();
}
int main(void){
test01();
return 0;
}
优点:
(1)能够协同调用者和被调用者,在一定程度上降低了系统的耦合度
(2)增加更改代理类无需修改源代码,符合开闭原则,具有较好的可扩展性
缺点:
(1)代理实现可能较为复杂
(2)添加了中间件,运行速率可能有所下降
适用场景:
(1)为其他对象提供一种代理以控制对这个对象的访问
(2)装饰模式
又称装饰器,包装模式,向一个现有的对象添加新的功能,同时又不改变其结构。
#include <iostream>
using namespace std;
//一般情况下 用继承实现类的功能拓展
//装饰模式 可以动态给一个类增加功能
//抽象英雄
class AbstractHero{
public:
virtual void ShowStatus() = 0;
public:
int mHp;//血量
int mAt;//攻击力
};
//具体英雄
class HeroA : public AbstractHero{
public:
HeroA(){
mHp= 10 ;
mAt= 1 ;
}
virtual void ShowStatus(){
cout << "血量:" << mHp << endl;
cout << "攻击:" << mAt << endl;
}
};
//英雄穿上某个装饰物 那么他还是个英雄
class AbstractEquipment : public AbstractHero{
public:
AbstractEquipment(AbstractHero* hero){
this->pHero = hero;
}
virtual void ShowStatus(){}
public:
AbstractHero* pHero;
};
//防具
class Equipment1 : public AbstractEquipment{
public:
Equipment1(AbstractHero* hero) :AbstractEquipment(hero){}
//增加防御
void AddEquipment1(){
cout << "英雄穿上防具之后..." << endl;
this->mHp = this->pHero->mHp+100;
this->mAt = this->pHero->mAt;
delete this->pHero;
}
virtual void ShowStatus(){
AddEquipment1(); //额外增加功能
cout << "血量:" << mHp << endl;
cout << "攻击:" << mAt << endl;
}
};
//武器
class Equipment2 : public AbstractEquipment{
public:
Equipment2(AbstractHero* hero) :AbstractEquipment(hero){}
//增加额外功能
void AddEquipment2(){
cout << "英雄装备武器之后..." << endl;
this->mHp = this->pHero->mHp;
this->mAt = this->pHero->mAt + 5;
delete this->pHero;
}
virtual void ShowStatus(){
AddEquipment2();
cout << "血量:" << mHp << endl;
cout << "攻击:" << mAt << endl;
}
};
void test01(){
AbstractHero* hero = new HeroA;
hero->ShowStatus();
cout << "---------------" << endl;
//给英雄装备防具
hero = new Equipment1(hero);
hero->ShowStatus();
cout << "---------------" << endl;
//给英雄装备武器
hero = new Equipment2(hero);
hero->ShowStatus();
}
int main(){
test01();
return 0;
}
优点:
(1)扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加
(2)可以通过一种动态方式扩展一个对象而无需更改结构
(3)可以对对象进行多次装饰
(4)具体构建类(此处指英雄)和具体装饰类(这里指装备)可以独立变化,符合开闭原则
缺点:
(1)装饰模式会产生很多小对象,小对象若不即时释放会影响性能
适用场景:
(1)动态,透明的更改单个对象
(2)不能继承或不应该采用继承的场景可以用装饰模式
(3)外观模式
外观模式隐藏系统的复杂性,向客户端提供了一个可以访问系统的接口。相当于有个高层接口负责好了内部的业务逻辑,只需要调用此接口即可。
#include <iostream>
using namespace std;
//电视机
class Television{
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 Mircophone{
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;}
};
class KTVMode{
public:
KTVMode(){
pTv = new Television;
pLight = new Light;
pAudio = new Audio;
pMicrophone = new Mircophone;
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();
}
~KTVMode(){
delete pTv;
delete pLight;
delete pAudio;
delete pMicrophone;
delete pDvd;
}
public:
Television* pTv;
Light* pLight;
Audio* pAudio;
Mircophone* pMicrophone;
DVDPlayer* pDvd;
};
//无需自己设置各种设备,点击设置模式即可实现
void test01(){
KTVMode* ktv = new KTVMode;
ktv->OnKTV();
}
int main(void)
{
test01();
return 0;
}
优点:
(1)屏蔽了子系统组件,减少了客户端所需处理的对象数目,客户端代码简单
(2)实现了子系统与客户端之间的解耦合,减少了相互依赖
(3)一个子系统的修改对其它子系统没有影响,提高了安全性
缺点:
(1)增加新的子系统可能需要修改外观类的源代码,违背了开闭原则,维护难度大
(2)限制了客户端对子系统类的访问,减少了灵活性(客户端操作空间少了,相对而言提高了安全性)
适用场景:
(1)复杂系统需要简单入口使用
(2)子系统相对独立
(3)预防低水平人员带来的风险(操作空间少,仅需要调用个别接口)
(4)适配器模式
作为两个不兼容的接口之间的桥梁。某函数不支持双参数,可以通过适配器实现一些奇奇怪怪的功能。比如一只老虎添加了适配器之后可以实现飞行功能。
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
//适配器模式 就是将已经写好的接口,但是这个接口不符合需求
//将写好的接口转换成目标接口
struct MyPrint{
void operator()(int v1,int v2){//运算符重载
cout << v1 + v2 << endl;
}
};
class Target{
public:
virtual void operator()(int v) = 0;
};
//写适配器
class Adapter : public Target{
public:
Adapter(int param){
this->param = param;
}
virtual void operator()(int v){
print(v, param);
//cout<<"aaa"<<endl;
}
public:
MyPrint print;
int param;
};
//MyBind2nd
Adapter MyBind2nd(int v){
return Adapter(v);
}
int main(void){
vector<int> v;
for (int i = 0; i < 10;i++){
v.push_back(i);
}
for_each(v.begin(), v.end(), MyBind2nd(10));
return 0;
}
优点:
(1)目标类和适配者类解耦,引入适配器类来重用现有的适配者类,无需修改原有结构
(2)增加了类的透明性和复用性
(3)灵活性和扩展性都很高,可以很方便的更换适配器,符合开闭原则
(4)可以让两个没有关联的类一起运行
缺点:
(1)适配器中置换适配者类的某些方法比较麻烦
(2)过多的使用适配器,系统会非常凌乱,比如明明调用A接口,却一起实现了B接口的功能,,出错的话维护难度大
适用场景:
(1)系统需要使用现有类,而接口不符合需要,甚至没有类的源代码
(2)将一些没有关联的类连接起来
(3)适配器并非设计时产生的,而是为了解决正在使用的项目所存在的问题
行为型模式
用来对类或对象怎么交互和分配职责进行描述
(1)模板方法模式
定义一个操作中算法的框架,将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法结构的同时即可重定义该算法的某些特定步骤
#include<iostream>
using namespace std;
class DrinkTemplate{
public:
//煮水
virtual void BoildWater() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加辅助料
virtual void AddSomething() = 0;
//模板方法
void Make(){
BoildWater();
Brew();
PourInCup();
AddSomething();
}
};
//冲泡咖啡
class Coffee : public DrinkTemplate{
public:
virtual void BoildWater(){
cout << "煮山泉水..." << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡咖啡..." << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "咖啡倒入杯中..." << endl;
}
//加辅助料
virtual void AddSomething(){
cout << "加糖,加牛奶,加点醋..." << endl;
}
};
//冲泡茶水
class Tea : public DrinkTemplate{
public:
virtual void BoildWater(){
cout << "煮自来水..." << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡铁观音..." << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "茶水倒入杯中..." << endl;
}
//加辅助料
virtual void AddSomething(){
cout << "加糖..加柠檬...加生姜..." << endl;
}
};
void test01(){
Tea* tea = new Tea;
tea->Make();
cout << "-----------" << endl;
Coffee* coffee = new Coffee;
coffee->Make();
}
int main(){
test01();
return 0;
}
优点:
(1)封装不变部分,扩展可变部分
(2)代码复用程度高,便于维护
(3)行为由父类控制,子类实现,符合单一职责和开闭原则
缺点:
(1)每一个不同地实现都需要一个子类,类个数可能过多
适用场景:
(1)存在多个具有同样操作步骤地应用场景,但具体操作细节不同
(2)命令模式
将一个请求封装成一个对象,从而可以用不同地请求对客户进行参数化。命令模式可以将请求发送者和接收者解耦,两者之间没有直接引用关系,发送者只需要发生命令,而无需知道如何完成。这里用服务器和客户端比较好理解,可以理解为http协议请求,你只需要发送get请求给服务器,服务器会自动返回信息。
#include <iostream>
#include <queue>
using namespace std;
//协议处理类
class HandleClientProtocol{
public:
//处理增加金币
void AddMoney(){cout << "给玩家增加金币!" << endl;}
//处理玩家升级
void addLevel(){cout << "给玩家升级!" << endl;}
};
//命令接口
class AbstractCommand{
public:
virtual void handle() = 0; //处理客户端请求的接口
};
//处理增加金币请求
class AddMoneyCommand :public AbstractCommand{
public:
AddMoneyCommand(HandleClientProtocol* protocol){
this->pProtocol = protocol;
}
virtual void handle(){
this->pProtocol->AddMoney();
}
public:
HandleClientProtocol* pProtocol;
};
//处理玩家升级的请求
class AddLevelCommand : public AbstractCommand{
public:
AddLevelCommand(HandleClientProtocol* protocol){
this->pProtocol = protocol;
}
virtual void handle(){
this->pProtocol->addLevel();
}
public:
HandleClientProtocol* pProtocol;
};
//服务器程序
class Serser{
public:
void addRequest(AbstractCommand* command){
mCommands.push(command);
}
void startHandle(){
while (!mCommands.empty()){
AbstractCommand* command = mCommands.front();
command->handle();
mCommands.pop();
}
}
public:
queue<AbstractCommand*> mCommands;
};
void test01(){
HandleClientProtocol* protocol = new HandleClientProtocol;
//客户端增加金币的请求
AbstractCommand* addmoney = new AddMoneyCommand(protocol);
//客户端升级请求
AbstractCommand* addlevel = new AddLevelCommand(protocol);
Serser* server = new Serser;
//将客户端请求加入到处理的队列中
server->addRequest(addmoney);
server->addRequest(addlevel);
//服务器开始处理请求
server->startHandle();
}
int main(){
test01();
return 0;
}
优点:
(1)降低系统耦合度,请求者和接收者之间完全解耦
(2)新命令可以很容易添加,无需修改源代码,满足开闭原则
缺点:
(1)可能会有过多的具体命令类
适用场景:
(1)请求者无需知道接收者的任何信息
(2)系统需要将一组操作组合在一起形成宏命令
(3)策略模式
定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。可以看成诸葛亮的锦囊妙计,一个锦囊代表一个策略;卡牌游戏的一张卡触发的效果也算是一个策略。
#include <iostream>
using namespace std;
//抽象武器 武器策略
class WeaponStrategy{
public:
virtual void UseWeapon() = 0;
};
class Knife : public WeaponStrategy{
public:
virtual void UseWeapon(){
cout << "使用匕首!" << endl;
}
};
class AK47 :public WeaponStrategy{
public:
virtual void UseWeapon(){
cout << "使用AK47!" << endl;
}
};
class Character {
public:
void setWeapon(WeaponStrategy* weapon){
this->pWeapon = weapon;
}
void ThrowWeapon(){
this->pWeapon->UseWeapon();
}
public:
WeaponStrategy* pWeapon;
};
void test01(){
//创建角色
Character* character = new Character;
//武器策略
WeaponStrategy* knife = new Knife;
WeaponStrategy* ak47 = new AK47;
character->setWeapon(knife);
character->ThrowWeapon();
character->setWeapon(ak47);
character->ThrowWeapon();
delete ak47;
delete knife;
delete character;
}
int main(){
test01();
return 0;
}
优点:
(1)算法可以自由切换
(2)避免使用多重条件判断
(3)扩展性良好,符合开闭原则
缺点:
(1)策略类数量可能过多,细小的变化都将导致系统要增加一个新的具体策略类
(2)所有策略类都需要对外暴露
适用场景:
(1)一个系统需要动态地在几种算法中选择一种
(4)观察者模式
当对象间存在一对多关系时,则使用观察者模式。比如当一个对象被修改时,则会自动通知依赖它的对象。这个案例非常多,比如红绿灯的变化会改变车辆的状态,拍卖会,警报等。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者。
#include <iostream>
#include <list>
using namespace std;
//抽象的英雄 抽象的观察者
class AbstractHero{
public:
virtual void Update() = 0;
};
//具体英雄 具体观察者
class HeroA :public AbstractHero{
public:
HeroA(){
cout << "英雄A正在攻击BOSS ..." << endl;
}
virtual void Update(){
cout << "英雄A停止攻击,进入待机状态..." << endl;
}
};
class HeroB :public AbstractHero{
public:
HeroB(){
cout << "英雄B正在攻击BOSS ..." << endl;
}
virtual void Update(){
cout << "英雄B停止攻击,进入待机状态..." << endl;
}
};
class HeroC :public AbstractHero{
public:
HeroC(){
cout << "英雄C正在攻击BOSS ..." << endl;
}
virtual void Update(){
cout << "英雄C停止攻击,进入待机状态..." << endl;
}
};
//观察目标抽象
class AbstractBoss{
public:
//添加观察者
virtual void addHero(AbstractHero* hero) = 0;
//删除观察者
virtual void deleteHero(AbstractHero* hero) = 0;
//通知所有观察者
virtual void notify() = 0;
};
//具体的观察者 BOSSA
class BOSSA : public AbstractBoss{
public:
virtual void addHero(AbstractHero* hero){
pHeroList.push_back(hero);
}
//删除观察者
virtual void deleteHero(AbstractHero* hero){
pHeroList.remove(hero);
}
//通知所有观察者
virtual void notify(){
for (list<AbstractHero*>::iterator it = pHeroList.begin(); it != pHeroList.end();it ++){
(*it)->Update();
}
}
public:
list<AbstractHero*> pHeroList;
};
void test01(){
//创建观察者
AbstractHero* heroA = new HeroA;
AbstractHero* heroB = new HeroB;
AbstractHero* heroC = new HeroC;
//创建观察目标
AbstractBoss* bossA = new BOSSA;
bossA->addHero(heroA);
bossA->addHero(heroB);
bossA->addHero(heroC);
cout << "heroB阵亡..." << endl;
bossA->deleteHero(heroB);
cout << "Boss阵亡...通知其他英雄停止攻击..." << endl;
bossA->notify();
}
int main(){
test01();
return 0;
}
优点:
(1)观察者和被观察者是抽象耦合的
(2)建立了一套触发机制
(3)简化了一对多的设计难度
缺点:
(1)如果一个被观察对象有很多直接或间接地观察者的话,通知花费的总时间会较多
(2)不能有循环依赖,否则会重复通知最后崩溃
(3)观察者仅仅知道发生了什么变化,但不知道如何变化的
适用场景:
(1)一个对象改变将导致其它对象也发生改变,而不需要知道具体由多少对象改变,降低耦合度
(2)需要创建触发链,A影响B,B影响C
(3)一个抽象模型有两方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使他们可以各自独立的改变和复用。(一个对象改变会引起另一个对象改变,就可以看成观察者依赖于观察对象)
设计模式需要大量的编程项目积累才能有比较深刻的感悟,不限语言,在于思想。
此文基于黑马程序员的C++设计模式课程(主要是例子)与菜鸟教程