设计模式的笔记学习。
设计模式的主流开发原则:
主流的设计模式有23种,主要介绍以下几种,其他方法等遇到了再学习:
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。也就是抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
遵循这个原则的子类才有资格说继承自父类
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。简单的说就是接口应该小而完备。
根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
尽量使用对象组合(class A中包含classB),而不是继承来达到复用的目的。简单的说就是少用继承,而多用对象组合。
合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承。
一个软件实体应当尽可能少地与其他实体发生相互作用
如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
一个好的面向对象的设计是应对变化,提高复用。
设计模式的关键是寻找变化点,并在变化点应用设计模式。所以一个需要应用设计模式的软件体系,需要既有稳定的地方,又有变化的地方。
“什么时候、什么地点应用设计模式”比“理解设计模式结构本身”更为重要。
重构关键的技法:
注意,基类必须给出一个虚析构函数。(原因看C++那篇)
“组件协作”模式通过晚期绑定,来实现框架与应用程序之 间的松耦合,是二者之间协作时常用的模式。
在现实生活中,很多事情都包含几个实现步骤,例如请客吃饭,无论吃什么,一般都包含点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单 --> 吃东西 --> 买单。在这三个步骤中,点单和买单大同小异,最大的区别在于第二步——吃什么?吃面条和吃满汉全席可大不相同,如图1所示:
在软件开发中,有时也会遇到类似的情况,某个方法的实现需要多个步骤(类似“请客”),其中有些步骤是固定的(类似“点单”和“买单”),而有些步骤并不固定,存在可变性(类似“吃东西”)。为了提高代码的复用性和系统的灵活性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计,在模板方法模式中,将实现功能的每一个步骤所对应的方法称为基本方法(例如“点单”、“吃东西”和“买单”),而调用这些基本方法同时定义基本方法的执行次序的方法称为模板方法(例如“请客”)。在模板方法模式中,可以将相同的代码放在父类中,例如将模板方法“请客”以及基本方法“点单”和“买单”的实现放在父类中,而对于基本方法“吃东西”,在父类中只做一个声明,将其具体实现放在不同的子类中,在一个子类中提供“吃面条”的实现,而另一个子类提供“吃满汉全席”的实现 。通过使用模板方法模式,一方面提高了代码的复用性,另一方面还可以利用面向对象的多态性,在运行时选择一种具体子类,实现完整的“请客”方法,提高系统的灵活性和可扩展性。
模板方法模式由两部分结构组成:抽象父类和具体的实现子类。通常在抽象父类中封装了子类的算法框架,也包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
模板方法模式定义如下:
定义一个操作中的算法的结构(稳定的,骨架也就是执行顺序),而将一些步骤延迟 (变化)到子类中。Template Method使得子类可以不改变 (复用)一个算法的结构即可重写该算法的 某些特定步骤。
个人总结:简单来说就是父类定义ABC三个动作;其中A、C动作由于大家都一样,所以父类直接实现,而B动作由于不同的业务会导致动作的不同,所以由子类自己实现的(所以B只需要在父类中定义成虚函数,父类不需要实现),然后在父类中需要在定义一个run方法,用来定义动作的执行次序。子类只需要实现父类的虚函数即可。
如上图所示,红色的部分是稳定的部分,蓝色的部分是变化的部分(会随着业务的不同而改变),框架开发人员会把程序的主流程也设计进框架内(父类),应用开发人员就不需要考虑设计程序主流程了,只需要开发步骤2、4。
/* 模板方法模式 */
class father
{
public:
void A(){//稳定
...;
}
virtual void B()=0;//支持变化的。虚函数,由子类实现
void C(){//稳定
...;
}
void run()//稳定
{
A();
B();
C();
}
};
从上面的代码可以看出,run方法是稳定的,但是稳定中又带有变化(B方法支持重写)。
模版方法的缺陷:
需要假定算法的结构(也就是run方法)是稳定的,也就是说所有子类的执行步骤都是一样的,才能使用模板方法的设计模式。
在看懂了前面对模板方法的介绍后,再来看下面的要点总结,不然你看不懂:
代码案例:
/* 模板方法模式案例
* 假定一个场景:去餐馆吃饭的场景
* 该场景包含了三个动作:点单 吃东西 买单
* 点单和买单大同小异,最大的区别在于第二步——吃什么?
* */
#include
#include
using namespace std;
class Abstract_eat
{
public:
void Order()
{
cout<<"点菜!"<<endl;
}
void Pay()
{
cout<<"买单!"<<endl;
}
void Run()//稳定
{
Order();//稳定
Eat();//变化,不同的业务这里体现不同
Pay();//稳定
}
virtual ~Abstract_eat(){}
protected:
virtual void Eat()=0;
};
/* 应用人员只需要重写虚函数就好了 */
class Tom_eat: public Abstract_eat
{
void Eat()
{
cout<<"吃面!"<<endl;
}
};
class Bob_eat: public Abstract_eat
{
void Eat()
{
cout<<"吃麦当劳!"<<endl;
}
};
int main() {
Abstract_eat * Bob = new Bob_eat();
cout<<"鲍勃的吃东西的执行流程:"<<endl;
Bob->Run();
cout<<endl;
Abstract_eat * Tom = new Tom_eat();
cout<<"汤姆的吃东西的执行流程:"<<endl;
Tom->Run();
return 0;
}
输出结果:
鲍勃的吃东西的执行流程:
点菜!
吃麦当劳!
买单!
汤姆的吃东西的执行流程:
点菜!
吃面!
买单!
在策略模式中,我们可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里,每一个封装算法的类我们都可以称之为一种策略(Strategy),为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做规则的定义,而每种算法则对应于一个具体策略类。
策略模式的主要目的是将算法的定义与使用分开,也就是将算法的行为和环境分开,将算法的定义放 在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合“依赖倒转原则”。在出现新的算法时,只需要增加一个新的实现了抽象策略类的具体策略类即可。
策略模式定义如下:
定义一系列算法,把它们一个个封装起来,并且使它们可互 相替换(变化)。该模式使得算法可独立于使用它的客户程 序(稳定)而变化(扩展,通过子类化)。
在策略模式中包含如下几个角色:
个人总结:简单的说就是在算法的实现依赖于策略,策略可以根据继承抽象父类选择不同的策略来实现。具体实现其实就是定义一个环境类(也就是使用不同方法的类),在定义一个抽象的父类(作为接口),具体实现的子类(也称具体策略类),重写抽象父类的虚函数(重写接口),环境类通过调用父类中的虚方法(实现多态)来调用不同的子类,从而实现灵活的选择不同的方法。
如果看懂了上面的介绍,下面的结构图应该就能看懂了(图中每个类的介绍看上面)
要点总结:
策略模式的好处用扩展的方式面对未来需求的变化。只需要新写一个类,就能扩展一种方法。
策略模式缺陷:
如果你的环境类不会改变,也就是一定只会用abc三种算法,不会用到其他算法,那么就不需要用策略模式。
/*
* 根据结构图抽象的实现
*/
//它为所支持的算法声明了抽象方法,是所有策略类的父类
class Strategy
{
public:
virtual void algorithm()=0;//抽象接口
virtual ~Strategy(){};
};
class Context//使用算法(策略)的角色,用于解决某个具体问题
{
private:
//具体策略
Strategy * m_strategy;
public:
void SetStrategy(Strategy * strategy)
{
//获取具体的方法(父类指针指向子类对象)
this->m_strategy = strategy;
}
void UseStrategy()
{
m_strategy->algorithm();//多态
}
};
/*
* 具体策略类
*/
class Strategy_A:public Strategy
{
public:
void algorithm()
{
cout<<"策略A"<<endl;
}
};
class Strategy_B:public Strategy
{
public:
void algorithm()
{
cout<<"策略B"<<endl;
}
};
/*
* 策略模式案例
* 假设一个场景:有税率计算问题,每个国家的税率的计算公式都不同
* */
//抽象税率父类
class Strategy_Tex
{
public:
virtual void Calculate()=0;//抽象接口
virtual ~Strategy_Tex(){};
};
//税率的计算
class Context_SalesOrder
{
private:
//用于保存具体某一国家计算税率的方法的对象
Strategy_Tex * m_strategy;
public:
void SetStrategy(Strategy_Tex * strategy)
{
//获取某一国家的税率方法的对象(父类指针指向子类对象)
this->m_strategy = strategy;
}
//具体使用某一国家的税率的方法
void UseStrategy()
{
m_strategy->Calculate();//多态
}
};
/*
* 具体每个国家计算税率的方法:
*/
class CNTax:public Strategy_Tex
{
public:
void Calculate()
{
cout<<"公式A计算中国税率"<<endl;
}
};
class USTax:public Strategy_Tex
{
public:
void Calculate()
{
cout<<"公式B计算中国税率"<<endl;
}
};
//新写一个类,就能扩展一种计算某个国家的税率
class DETax:public Strategy_Tex
{
public:
void Calculate()
{
cout<<"公式C计算中国税率"<<endl;
}
};
int main()
{
Context_SalesOrder c = Context_SalesOrder();
CNTax * CNtax = new CNTax;
USTax * UStax = new USTax;
c.SetStrategy(CNtax);
c.UseStrategy();
c.SetStrategy(UStax);
c.UseStrategy();
}
输出结果:
公式A计算中国税率
公式B计算中国税率
观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
定义:
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式。
要点总结:
注意:一般不推荐使用多继承,使用多继承的唯一用于是:一个作为主的继承实现,其他继承作为接口实现。
/* 观察者模式案例
* 假定一个场景:有个任务会根据自身状态,会自动通知其他对象(订阅者/观察者)做出反应
* 任务就是目标,其他对象接收任务的通知就是观察者
* */
#include
#include
#include
#include
using namespace std;
//抽象观察者类
class Observer
{
public:
virtual void Update(int value)=0;
virtual ~Observer(){};
};
//具体观察者类
class aObserver: public Observer
{
void Update(int value)
{
cout<<"A观察者:已观察到目标改变状态为"<<value<<","<<"自身做出改变:"<<value*36<<endl;
}
};
class bObserver: public Observer
{
void Update(int value)
{
cout<<"B观察者:已观察到目标改变状态为"<<value<<","<<"自身做出改变:"<<value*10<<endl;
}
};
//目标类
class Subject
{
private:
list<Observer *> m_ObserverList;//抽象通知机制
public:
void runTask()
{
for(int i=0; i<5; ++i)//模拟任务
{
//通知观察者状态改变
Notify(i);
sleep(2);
}
}
//增加观察者
void Attach(Observer * observer)
{
m_ObserverList.push_back(observer);
}
//删除观察者
void Detach(Observer * observer)
{
m_ObserverList.remove(observer);
}
//该函数用于通知所有观察者
void Notify(int value)
{
for(auto & ob :m_ObserverList)
{
ob->Update(value);
}
}
};
int main() {
//实现观察者对象
aObserver aobserver;
bObserver bobserver;
//实现目标对象
Subject subject;
//添加订阅
subject.Attach(&aobserver);
subject.Attach(&bobserver);
//目标执行任务
subject.runTask();
return 0;
}
输出结果:
A观察者:已观察到目标改变状态为0,自身做出改变:0
B观察者:已观察到目标改变状态为0,自身做出改变:0
A观察者:已观察到目标改变状态为1,自身做出改变:36
B观察者:已观察到目标改变状态为1,自身做出改变:10
A观察者:已观察到目标改变状态为2,自身做出改变:72
B观察者:已观察到目标改变状态为2,自身做出改变:20
A观察者:已观察到目标改变状态为3,自身做出改变:108
B观察者:已观察到目标改变状态为3,自身做出改变:30
A观察者:已观察到目标改变状态为4,自身做出改变:144
B观察者:已观察到目标改变状态为4,自身做出改变:40
\
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
工厂模式的前身还有个简单工厂,但是违背了开闭原则,所以不使用。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象, 这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法模式定义如下:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。 Factory Method使得一个类的实例化延迟(目的:解耦, 手段:虚函数)到子类。
个人总结:简单来说就是不用new了,而是由子类工厂对象通过继承工厂基类的创造产品的函数,来决定是实例化哪个具体产品。具体来说有四个类,抽象工厂基类的创造产品函数返回值是抽象产品,而子类工厂返回具体的产品(具体的产品继承了抽象产品)。
在工厂方法模式结构图中包含如下几个角色:
工厂基类声明createproduct函数,返回值为抽象产品,具体工厂继承工厂基类,返回具体的具体产品。
要点总结:
缺点在于要求创建方法/参数相同
/* 工厂方法模式案例
* 假定一个场景:有个业务场景会需要使用不同的文件分割器来分割文件,文件分割器有很多种类型,比如图片分割,视频分割,TxT分割。
* 根据不同的业务需要,需要使用不同的分割器来分割。
* 因此需要工厂方法模式:按照业务的需要进行产生不同的分割器对象来完成业务需要
* */
#include
#include
using namespace std;
/* 抽象产品类 */
class Product_ISplitter
{
public:
virtual void split() = 0;
virtual ~Product_ISplitter(){};
};
/* 具体产品类 */
class Product_PictureSplitter:public Product_ISplitter
{
public:
virtual void split()
{
cout<<"图片分割"<<endl;
}
};
class Product_VideoSplitter:public Product_ISplitter
{
public:
virtual void split()
{
cout<<"视频分割"<<endl;
}
};
class Product_TxtSplitter:public Product_ISplitter
{
public:
virtual void split()
{
cout<<"Txt分割"<<endl;
}
};
/* 工厂基类 */
class Factory
{
public:
virtual Product_ISplitter * CreateProduct()=0;
virtual ~Factory(){}
};
/* 具体工厂类--与具体产品类对应 */
//生产图片分割器类的工厂
class ConcreteFactory_PictureSplitter:public Factory
{
public:
//注意这里返回的对象是抽象产品类
virtual Product_ISplitter * CreateProduct()
{
return new Product_PictureSplitter();
}
};
//生产视频分割器类的工厂
class ConcreteFactory_VideoSplitter:public Factory
{
public:
//注意这里返回的对象是抽象产品类
virtual Product_ISplitter * CreateProduct()
{
return new Product_VideoSplitter();
}
};
//生产视频分割器类的工厂
class ConcreteFactory_TxtSplitter:public Factory
{
public:
//注意这里返回的对象是抽象产品类
virtual Product_ISplitter * CreateProduct()
{
return new Product_TxtSplitter();
}
};
int main() {
//使用工厂方法模式生产不同的分割器
Factory * factory = new ConcreteFactory_VideoSplitter();//抽象工厂类的指针 指向 具体工厂类
//这里会根据上一条代码new的不同的具体工厂而产生不同的产品(利用了多态)。注意:这里也是用抽象产品类来接收具体产品类
Product_ISplitter * product = factory->CreateProduct();
product->split();//利用了产品类的多态的特性,执行不同产品的功能(具体产品类重写了抽象产品类的split函数)
return 0;
}
输出结果:
图片分割
视频分割
抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。
抽象工厂模式定义如下:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
个人总结:其实和工厂方法模式很像,工厂方法模式是抽象工厂模式的一个特例。具体区别在于:抽象工厂模式会生产多个有相互依赖关系的对象作为一簇产品。而工厂方法只会生产一个对象作为一个产品
在抽象工厂模式结构图中包含如下几个角色:
要点总结:
/* 工厂方法模式案例
* 假定一个场景:有个业务场景可能会使用不同的数据库在处理数据。并且每个数据库都有三个动作:连接,sql命令,读数据
* 根据不同的业务需要,需要使用不同的数据库
* 因此需要工厂方法模式:按照业务的需要进行产生不同的数据库来完成业务需要
* */
#include
#include
using namespace std;
/* 抽象产品类(数据库访问有关的基类) */
class DBConnect
{
public:
virtual void Connect() = 0;
virtual ~DBConnect(){};
};
class DBCommand
{
public:
virtual void Command() = 0;
virtual ~DBCommand(){};
};
class DBReader
{
public:
virtual void Reader() = 0;
virtual ~DBReader(){};
};
/* 具体产品类 */
//mysql相关
class MysqlConnect:public DBConnect
{
public:
virtual void Connect()
{
cout<<"mysql连接!"<<endl;
}
};
class MysqlCommand:public DBCommand
{
public:
virtual void Command()
{
cout<<"mysql命令!"<<endl;
}
};
class MysqlReader:public DBReader
{
public:
virtual void Reader()
{
cout<<"mysql数据读取!"<<endl;
}
};
//oracle相关
class oracleConnect:public DBConnect
{
public:
virtual void Connect()
{
cout<<"oracle连接!"<<endl;
}
};
class oracleCommand:public DBCommand
{
public:
virtual void Command()
{
cout<<"oracle命令!"<<endl;
}
};
class oracleReader:public DBReader
{
public:
virtual void Reader()
{
cout<<"oracle数据读取!"<<endl;
}
};
/* 工厂基类 */
class Factory
{
public:
virtual DBConnect * CreateDBConnect()=0;
virtual DBCommand * CreateDBCommand()=0;
virtual DBReader* CreateReader()=0;
virtual ~Factory(){}
};
/* 具体工厂类--与具体产品类对应 */
//Mysql相关的工厂
class MysqlFactory:public Factory
{
public:
//注意这里返回的对象是抽象产品类
virtual DBConnect * CreateDBConnect()
{
return new MysqlConnect();
}
virtual DBCommand * CreateDBCommand()
{
return new MysqlCommand();
}
virtual DBReader* CreateReader()
{
return new MysqlReader();
}
};
//Mysql相关的工厂
class oracleFactory:public Factory
{
public:
//注意这里返回的对象是抽象产品类
virtual DBConnect * CreateDBConnect()
{
return new oracleConnect();
}
virtual DBCommand * CreateDBCommand()
{
return new oracleCommand();
}
virtual DBReader* CreateReader()
{
return new oracleReader();
}
};
int main() {
//使用抽象工厂模式来生产不同数据库以满足业务需要
Factory * factory = new oracleFactory();
DBConnect * dbconnect = factory->CreateDBConnect();
DBCommand * dbcommand = factory->CreateDBCommand();
DBReader * dbreader = factory->CreateReader();
dbconnect->Connect();
dbcommand->Command();
dbreader->Reader();
cout<<endl;
factory = new MysqlFactory();
dbconnect = factory->CreateDBConnect();
dbcommand = factory->CreateDBCommand();
dbreader = factory->CreateReader();
dbconnect->Connect();
dbcommand->Command();
dbreader->Reader();
}
输出结果
oracle连接!
oracle命令!
oracle数据读取!
mysql连接!
mysql命令!
mysql数据读取!
前面的工厂模式绕过new是解决紧耦合问题,而单例模式绕过new是业务需要或者是性能的原因
动机:
单例模式分为懒汉模式和饿汉模式:
实现方法:
构造函数和拷贝构造函数都设置成private,并设置getInstance方法获取对象。
单例模式定义如下:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
/*
* 单例模式案例
* */
#include
#include
#include
using namespace std;
class Single1
{
public:
static Single1 * GetInstance();
private:
static Single1 * m_instance;
Single1(){};
Single1(const Single1 & s){};
};
/* 饿汉模式:即迫不及待,在程序运行时立即初始化(比较简单)*/
Single1 * Single1::m_instance = new Single1();
Single1 * Single1::GetInstance()
{
return m_instance;
}
/* 懒汉模式:非常懒,不用的时候不去初始化,所以在第一次被使用时才进行初始化。有三种初始化方法*/
class Single2
{
public:
static Single2 * GetInstance();
private:
static pthread_mutex_t m_lock;
static Single2 * m_instance;
Single2(){};
Single2(const Single2 & s){};
};
//1、线程非安全版本
Single2 * Single2::GetInstance()
{
if(m_instance == nullptr)
{
m_instance = new Single2();
}
return m_instance;
}
//2、线程安全版本,但是锁的代价过高
Single2 * Single2::GetInstance()
{
pthread_mutex_lock(&m_lock);
if(m_instance == nullptr)
{
m_instance = new Single2();
}
pthread_mutex_unlock(&m_lock);
return m_instance;
}
//3、双检查锁,但由于内存读写reorder不安全
Single2 * Single2::GetInstance()
{
if(m_instance == nullptr)
{
pthread_mutex_lock(&m_lock);
if(m_instance == nullptr)
{
m_instance = new Single2();
}
pthread_mutex_unlock(&m_lock);
}
return m_instance;
}
int main()
{
Single2 * a = Single2::GetInstance();
}
注意:在案例中使用了双检查锁
为什么要用双检测,只检测一次不行吗?
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
并且,双检查锁会有reorder现象:编译器在正式编译时,不一定是按照先给内存分配对象,再将内存返回给m_instance,而有可能会先给m_instance分配一个空的内存,再将对象分配给m_instance,而在给他分配空的内存的时候,第二个线程来了,此时就会得到一个空内存的m_instance,会出错。
动机:
在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等)无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问。
代理模式定义如下:
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
核心思想:增加一个间接层,来实现一些功能(性能优化、无法直接访问)。
在真正业务处理时:proxy的设计一般会比较复杂。
要点总结:
外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
不知道大家有没有比较过自己泡茶和去茶馆喝茶的区别,如果是自己泡茶需要自行准备茶叶、茶具和开水,而去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事。
因此,在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。 外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大,如下图(A)所示;而引入外观类之后,客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度,如下图(B)所示:
外观模式定义如下:
为子系统中的一组接口提供一个统一(稳定)的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
个人总结: 一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开(解耦合),使得客户类只需要与外观类打交道就可以实现与内部进行通信,而不需要与子系统内部的很多对象打交道。
要点总结:
动机:
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为"同一种算法在多种集合对象上进行操作”提供了可能。
在软件开发中,我们经常需要使用聚合对象来存储一系列数据。聚合对象拥有两个职责:一是存储数据;二是遍历数据。从依赖性来看,前者是聚合对象的基本职责;而后者既是可变化的,又是可分离的。因此,可以将遍历数据的行为从聚合对象中分离出来,封装在一个被称之为“迭代器”的对象中,由迭代器来提供遍历聚合对象内部数据的行为,这将简化聚合对象的设计,更符合“单一职责原则”的要求。
定义:
提供一种方法来访问聚合对象,而不用暴露(稳定)这个对象的内部表示。