设计模式—创建型模式
贴一个宝藏网站:https://refactoringguru.cn/design-patterns/creational-patterns
设计模式的分类
创建者模式
1、工厂模式
1.1、简单工厂模式
定义: 定义了一个 创建对象的类,这个类封装了实例化对象的行为。
代码示例:
#include <iostream>
using namespace std;
enum CarType{BENZ, BMW};
class Car//车类
{
public:
virtual void createdCar(void) = 0;
};
class BenzCar : public Car //奔驰车
{
public:
BenzCar()
{
cout<<"Benz::Benz()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BenzCar::createdCar()"<<endl;
}
~BenzCar()
{
}
};
class BmwCar : public Car //宝马车
{
public:
BmwCar()
{
cout<<"Bmw::Bmw()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BmwCar::createdCar()"<<endl;
}
};
class CarFactory //车厂
{
public:
Car* createSpecificCar(CarType type)
{
switch(type)
{
case BENZ: //生产奔驰车
return (new BenzCar());
break;
case BMW: //生产宝马车
return (new BmwCar());
break;
default:
return NULL;
break;
}
}
};
int main(int argc, char** argv)
{
CarFactory carfac;
Car* specificCarA = carfac.createSpecificCar(BENZ);
Car* specificCarB = carfac.createSpecificCar(BMW);
delete specificCarA; delete specificCarB;
return 0;
}
简单工厂存在的问题与解决方法:
简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?我们可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。这种方法也就是我们接下来要说的工厂方法模式。
1.2、工厂方法模式
定义: 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
也就是定义一个抽象工厂,其定义了产品的生产接口,但不负责具体的产品,将生产任务交给不同的派生类工厂。这样不用通过指定类型来创建对象了。
代码示例:
#include <iostream>
using namespace std;
class Car//车类
{
public:
virtual void createdCar(void) = 0;
};
class BenzCar : public Car //奔驰车
{
public:
BenzCar()
{
cout<<"Benz::Benz()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BenzCar::createdCar()"<<endl;
}
~BenzCar()
{
}
};
class BmwCar : public Car //宝马车
{
public:
BmwCar()
{
cout<<"Bmw::Bmw()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BmwCar::createdCar()"<<endl;
}
};
class Factory//车厂
{
public:
virtual Car* createSpecificCar(void) = 0;
};
class BenzFactory : public Factory//奔驰车厂
{
public:
virtual Car* createSpecificCar(void)
{
return (new BenzCar());
}
};
class BmwFactory : public Factory//宝马车厂
{
public:
virtual Car* createSpecificCar(void)
{
return (new BmwCar());
}
};
int main(int argc, char** argv)
{
Factory* factory = new BenzFactory();
Car* specificCarA = factory->createSpecificCar();
factory = new BmwFactory();
Car* specificCarB = factory->createSpecificCar();
delete factory; delete specificCarA; delete specificCarB;
return 0;
}
其实这个模式的好处就是,如果你现在想增加一个功能,只需做一个实现类就OK了,无需去改动现成的代码。这样做,拓展性较好!
工厂方法存在的问题与解决方法:
客户端需要创建类的具体的实例。简单来说就是用户要订奔驰汽车,他必须去奔驰车厂,想订宝马汽车,必须去宝马车厂。 当奔驰车厂和宝马车厂发生变化了,用户也要跟着变化,这无疑就增加了用户的操作复杂性。为了解决这一问题,我们可以把工厂类抽象为接口,用户只需要去找默认的工厂提出自己的需求(传入参数),便能得到自己想要产品,而不用根据产品去寻找不同的工厂,方便用户操作。这也就是我们接下来要说的抽象工厂模式。
1.3、抽象工厂模式
定义: 定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。
从上面类图结构中可以清楚的看到如何在工厂方法模式中通过增加新产品接口来实现产品的增加的。
代码示例:
#include <iostream>
using namespace std;
class Car//车类
{
public:
virtual void createdCar(void) = 0;
};
class BenzCar : public Car //奔驰车
{
public:
BenzCar()
{
cout<<"Benz::Benz()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BenzCar::createdCar()"<<endl;
}
~BenzCar()
{
}
};
class BmwCar : public Car //宝马车
{
public:
BmwCar()
{
cout<<"Bmw::Bmw()"<<endl;
}
virtual void createdCar(void)
{
cout<<"BmwCar::createdCar()"<<endl;
}
};
class HighCar //高配版车型
{
public:
virtual void createdCar(void) = 0;
};
class HighBenzCar : public HighCar //高配奔驰车
{
public:
HighBenzCar()
{
cout<<"HighBenzCarBenz::Benz()"<<endl;
}
virtual void createdCar(void)
{
cout<<"HighBenzCar::createdCar()"<<endl;
}
};
class HighBmwCar : public HighCar //高配宝马车
{
public:
HighBmwCar()
{
cout<<"HighBmwCar::Bmw()"<<endl;
}
virtual void createdCar(void)
{
cout<<"HighBmwCar::createdCar()"<<endl;
}
};
class Factory//车厂
{
public:
virtual Car* createSpecificCar(void) = 0;
virtual HighCar* createdSpecificHighCar(void) = 0;
};
class BenzFactory : public Factory//奔驰车厂
{
public:
virtual Car* createSpecificCar(void)
{
return (new BenzCar());
}
virtual HighCar* createdSpecificHighCar(void)
{
return (new HighBenzCar());
}
};
class BmwFactory : public Factory//宝马车厂
{
public:
virtual Car* createSpecificCar(void)
{
return (new BmwCar());
}
virtual HighCar* createdSpecificHighCar(void)
{
return (new HighBmwCar());
}
};
int main(int argc, char** argv)
{
Factory* factory = new BenzFactory();
Car* specificCar = factory->createSpecificCar();
HighCar* spcificHighCar = factory->createdSpecificHighCar();
delete factory; delete specificCar; delete spcificHighCar;
return 0;
}
1.4、三种工厂模式的使用选择
简单工厂 : 用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
抽象工厂 :用来生产不同产品族的全部产品。(支持拓展增加产品;支持增加产品族)
2、单例模式
定义: 确保一个类最多只有一个实例,并提供一个全局访问点。
单例模式可以分为两种:预加载和懒加载
2.1、预加载(饿汉式)
就是还没有使用该单例对象,但是,该单例对象就已经被加载到内存了。
代码示例:
class PreloadSingleton
{
private:
//构造函数为私有,其他的类无法实例化单例类的对象
PreloadSingleton() {};
//创建静态对象实例
static PreloadSingleton instance = new PreloadSingleton();
public:
//静态函数获取对象实例
static PreloadSingleton getInstance() {
return instance;
}
}
很明显,没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
2.2、懒加载(懒汉式)
为了避免内存的浪费,我们可以采用懒加载,即用到该单例对象的时候再创建。
代码示例:
class singleton //实现单例模式的类
{
private:
singleton(){} //私有的构造函数
static singleton* Instance;
public:
static singleton* GetInstance()
{
if (Instance == NULL) //判断是否第一调用
Instance = new singleton();
return Instance;
}
};
缺点: 这个实现在单线程下是正确的,但在多线程情况下,如果两个线程同时首次调用GetInstance方法且同时检测到Instance是NULL,则两个线程会同时构造一个实例给Instance,这样就会发生错误。
2.3、线程安全的懒加载(双重检查锁)
思路: 只有在第一次创建的时候进行加锁,当Instance不为空的时候就不需要进行加锁的操作。
代码示例:
class singleton //实现单例模式的类
{
private:
singleton(){} //私有的构造函数
static singleton* Instance;
public:
static singleton* GetInstance()
{
if (Instance == NULL) //判断是否第一调用
{
Lock(); //表示上锁的函数
if (Instance == NULL)
{
Instance = new singleton();
}
UnLock() //解锁函数
}
return Instance;
}
};
3、生成器模式
定义: 封装一个复杂对象构造过程,并允许按步骤构造。
定义解释: 我们可以将生成器模式理解为,假设我们有一个对象需要建立,这个对象是由多个组件(Component)组合而成,每个组件的建立都比较复杂,但运用组件来建立所需的对象非常简单,所以我们就可以将构建复杂组件的步骤与运用组件构建对象分离,使用builder模式可以建立。
3.1、生成器模式的结构
生成器模式结构中包括四种角色:
(1)产品(Product):具体生产器要构造的复杂对象;
(2)抽象生成器(Bulider):抽象生成器是一个接口,该接口除了为创建一个Product对象的各个组件定义了若干个方法之外,还要定义返回Product对象的方法(定义构造步骤);
(3)具体生产器(ConcreteBuilder):实现Builder接口的类,具体生成器将实现Builder接口所定义的方法(生产各个组件);
(4)指挥者(Director):指挥者是一个类,该类需要含有Builder接口声明的变量。指挥者的职责是负责向用户提供具体生成器,即指挥者将请求具体生成器类来构造用户所需要的Product对象,如果所请求的具体生成器成功地构造出Product对象,指挥者就可以让该具体生产器返回所构造的Product对象。(按照步骤组装部件,并返回Product)
3.2、实例和代码分析
实例:比如我现在要生产大众和奇瑞两种汽车,我们知道要制造一辆汽车可不是件容易的事,汽车是个非常复杂的对象,我现在假定汽车由A,B,C,D四个模块组成,我要生产一辆汽车,那么必须先做出A模块,再做出B模块,然后做出C模块,最后做出D模块。然后把这些模块组装起来,就成了一辆汽车了。奇瑞和大众汽车的制造过程都是一样的! 那么我在具体实现中汽车设计师(指挥者类)先把汽车制造的整个制造过程规划好,然后下面汽车工人按照相同的制造过程就造好了大众和奇瑞两种汽车!汽车设计师只管设计和规划汽车的制造过程,具体每个过程的实现是由下面的汽车工人来做的,设计者只管设计,实施者只管实施,二者互补干扰! 设计只管对象的表示(建模),实施者只管具体创建过程的实施,这样就将对象的创建过程和这个对象的表示分开来了!
代码示例:
class Car
{
public:
virtual void CreatPartA() = 0;
virtual void CreatPartB() = 0;
virtual void CreatPartC() = 0;
virtual void CreatPartD() = 0;
};
class DazhongCar :public Car
{
public:
virtual void CreatPartA()
{
cout <<endl<<"大众汽车:"<<endl<< "大众汽车A部分" << endl;
}
virtual void CreatPartB()
{
cout << "大众汽车B部分" << endl;
}
virtual void CreatPartC()
{
cout << "大众汽车C部分" << endl;
}
virtual void CreatPartD()
{
cout << "大众汽车D部分" << endl;
}
};
class Qirui :public Car
{
public:
virtual void CreatPartA()
{
cout << endl << "奇瑞汽车:" << endl << "奇瑞汽车A部分" << endl;
}
virtual void CreatPartB()
{
cout << "奇瑞汽车B部分" << endl;
}
virtual void CreatPartC()
{
cout << "奇瑞汽车C部分" << endl;
}
virtual void CreatPartD()
{
cout << "奇瑞汽车D部分" << endl;
}
};
class Direct //指挥者类
{
public:
Direct(Car*Temp)
{
C = Temp;
}
void Create()
{
C->CreatPartA();
C->CreatPartB();
C->CreatPartC();
C->CreatPartD();
}
private:
Car*C;
};
int main(void)
{
Car*D = new DazhongCar;
Car*Q = new Qirui;
Direct *A = new Direct(D);
Direct *B = new Direct(Q);
A->Create();
B->Create();
delete D;
delete Q;
delete A;
delete B;
return 0;
3.3、生成器模式的优缺点
优点:
①将一个对象分解为各个组件
②将对象组件的构造封装起来
③可以控制整个对象的生成过程
缺点:
对不同类型的对象需要实现不同的具体构造器的类,这可能回答大大增加类的数量
生成器模式与工厂模式的不同:
生成器模式构建对象的时候,对象通常构建的过程中需要多个步骤,就像我们例子中的先有主机,再有显示屏,再有鼠标等等,生成器模式的作用就是将这些复杂的构建过程封装起来。工厂模式构建对象的时候通常就只有一个步骤,调用一个工厂方法就可以生成一个对象。
4、原型模式
定义: 通过复制现有实例来创建新的实例,无需知道相应类的信息。
简单地理解,其实就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一毛一样的新对象来使用;基本上这就是原型模式。关键字:Clone 。
更简单的说,就是在类中封装一个深拷贝当前实例的方法,返回的对象具有和当前实例对象相同的内容(深拷贝)。
常用场景:
基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式。
4.1、深拷贝和浅拷贝
浅拷贝:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。clone明显是深复制,clone出来的对象是是不能去影响原型对象的。
4.2、原型模式的结构和代码示例
Client:用户
Prototype:接口(抽象类),定义了克隆自身的接口
ConcretePrototype:具体的原型类,需要实现接口
可以看出原型模式还是比较简单的,重点在于Prototype接口和Prototype接口的实现类ConcretePrototype。原型模式的具体实现:一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法。
代码示例:
#ifndef _PROTOTYPE_H_
#define _PROTOTYPE_H_
class Prototype{
public:
Prototype();
virtual ~Prototype();
virtual Prototype* clone() = 0;
};
class ConcretePrototype:public Prototype{
public:
ConcretePrototype();
~ConcretePrototype();
Prototype* clone();
private:
ConcretePrototype(const ConcretePrototype&);
};
#endif
#include "Prototype.h"
#include <stdio.h>
Prototype::Prototype()
{
}
Prototype::~Prototype()
{
}
ConcretePrototype::ConcretePrototype()
{
}
ConcretePrototype::~ConcretePrototype()
{
}
ConcretePrototype::ConcretePrototype(const ConcretePrototype& c)
{
fprintf(stderr,"ConcretePrototype copy construct!\n");
}
Prototype* ConcretePrototype::clone()
{
return new ConcretePrototype(*this);
}
#include "Prototype.h"
int main()
{
Prototype* p = new ConcretePrototype();
Prototype* a = p->clone();
return 0;
}
4.3、原型模式的优缺点
优点:
①如果创建新的对象比较复杂,可以利用原型模式简化对象的创建过程,同时也能提高效率。
②简化对象的创建,无须理会创建过程。
③可以在程序运行时(对象属性发生了变化)获得一份内容相同的实例,他们之间不会相互干扰。
缺点:
①在深拷贝时可能需要比较复杂的代码。
②需要为每一个类配备一个克隆方法,而且该克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但是对已有的类进行改造时,需要修改源码,违背了“开闭原则”。
5、 对象池模式
定义: 对象池是一种对象复用技术。Mark Grand在《Patterns in Java》描述了对象池的设计模式,它通过管理和复用有限对象来共享某些稀少或必须付出昂贵代价的资源。
当程序中需要用到一定数量的对象,且创建对象开销比较大时可以考虑使用对象池模式。和线程池类型,需要预先创建一定数量的对象,上层使用时可以从池子中获取创建好的对象。本文的对象池模式实现可变参数构造对象,使用完成之后自动析构。
Reusable:被复用的对象或资源。
Client:复用对象的使用者,1个Client对象可以使用多个复用对象,一个复用对象只能同时被1个Client使用。
ReusablePool:对象池即复用对象的容器或集合,用来管理和存储可复用的对象。一般来说为保证对象复用的安全,对象池将按照Singleton的模式设计为全局唯一实例。
对象池的实现:
对象池类模板的实现。这个池在构造时会分配一大块(chunk)指定类的对象(块可理解为包括许多对象,即一堆对象),并通过acquireObject()方法交出对象。当客户用完这个对象时,会通过releaseObject()方法将其返回。如果调用了acquireObject,但是没有空闲的对象,池会分配另一块对象。
对象池实现中最难的一方面是要记录那些对象是空闲的,那些对象正在使用,这个实现采用了以下做法,即把空闲的对象保存在一个队列中。每次客户请求一个对象时,池就会把队列中的第一个对象交给该客户。这个池不会显式地跟踪正在使用的对象。它相信客户在用完对象后会正确地将对象交还到池中。另外,这个池会在一个向量中记录所有已分配的对象。这个向量尽在撤销池时才会用到,以便释放所有对象的内存,从而避免内存泄漏。
代码示例:
#pragma once
#include <memory>
#include <queue>
#include <vector>
#include <assert.h>
#ifndef nullptr
#define nullptr 0
#endif
template< typename T >
class ObjectPool
{
public:
//初始化块大小如果能预先估算出块大小则性能达到最大
//即:只分配一次内存只释放一次内存
ObjectPool(size_t ChunkSize = 32);
virtual ~ObjectPool();
//设置块大小
void SetChunkSize(size_t ChunkSize);
//模拟new关键字分配内存并调用构造函数
T* New();
//模拟delete关键字释放内存调用析构函数
void Delete(T* pT);
private:
//分配块大小
void AllocateChunk(size_t ChunkSize);
//释放块
void ReleaseChunk();
private:
//空闲池
std::queue< T* > mFreePool;
//块池
std::vector< T* > mChunkPool;
//当前块大小
size_t mChunkSize;
};
template< typename T >
ObjectPool< T >::ObjectPool(size_t ChunkSize) //初始化块池大小
:mChunkSize(ChunkSize)
{}
template< typename T >
ObjectPool< T >::~ObjectPool()// 析构
{
ReleaseChunk();
}
template< typename T >
void ObjectPool< T >::SetChunkSize(size_t ChunkSize)//如果输入长度为零,则返回错误
{
if (0 == ChunkSize)
{
assert(false);
return;
}
mChunkSize = ChunkSize;
if (mFreePool.empty())
{
return;
}
if (mFreePool.size() < mChunkSize)
{
AllocateChunk(mChunkSize - mFreePool.size());
}
}
template< typename T >
T* ObjectPool< T >::New()//取mFreePool的队首元素,并调用其构造函数
{
if (mFreePool.empty())
{
AllocateChunk(mChunkSize);
}
if (mFreePool.empty())
{
assert(false);
return nullptr;
}
T* pT = mFreePool.front();
mFreePool.pop();
new(pT)T();
return pT;
}
template< typename T >
void ObjectPool< T >::Delete(T* pT)//将对象析构,并将指针重新压入mFreePool
{
if (nullptr == pT)
{
assert(false);
return;
}
pT->~T();
mFreePool.push(pT);
}
template< typename T >
void ObjectPool< T >::AllocateChunk(size_t ChunkSize)//申请ChunkSize长度的对象,并将所有指针压入mFreePool,头指针压入mChunkPool
{
if (0 == ChunkSize)
{
assert(false);
return;
}
T* pT = reinterpret_cast<T*>(malloc(ChunkSize * sizeof(T)));
if (nullptr == pT)
{
assert(false);
return;
}
for (size_t i = 0; i < ChunkSize; ++i)
{
mFreePool.push(pT + i);
}
mChunkPool.push_back(pT);
}
template< typename T >
void ObjectPool< T >::ReleaseChunk()//释放mChunkPool中所有头指针
{
for (size_t i = 0; i < mChunkPool.size(); ++i)
{
free(mChunkPool[i]);
mChunkPool[i] = nullptr;
}
mChunkPool.clear();
}