设计模式就相当于编程中的“孙子兵法”,是经过很久的时间以及各路大神总结出来的多种实用,高效的业务设计中的套路;
单例模式
一个类只能创建一个对象的情况下的一种设计模式(eg:服务器只有一个),即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
核心实现思想: 私有构造函数,拷贝构造,赋值重载;
饿汉模式
不管你将来用不用,程序启动时就创建一个唯一的实例对象。
// 饿汉模式
class Singleton
{
public:
static Singleton* GetInstance()
{
return &m_instance;
}
private:
// 构造函数私有
Singleton(){};
// C++98 防拷贝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
// or
// C++11 防拷贝
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton m_instance;
};
Singleton Singleton::m_instance; // 在程序入口之前,就完成单例对象的初始化,不管用不用;
优点:简单,线程安全(不需要配合锁)
缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等 等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时 非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
// 懒汉模式
class Singleton
{
public:
static Singleton* GetInstance() {
// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
if (nullptr == m_pInstance) {
m_mtx.lock();
if (nullptr == m_pInstance) {
m_pInstance = new Singleton();
}
m_mtx.unlock();
}
return m_pInstance;
}
private:
// 构造函数私有
Singleton() {};
// 防拷贝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
static Singleton* m_pInstance; // 静态,单例对象指针
static mutex m_mtx; //互斥锁
};
优点:延迟加载,程序启动更快;
缺点:原本非线程安全,加锁实现线程安全,但是锁的使用在多线程中性能消耗太大;
小结
- 很多线程or业务规模不大(程序整体启动不会很慢),用饿汉模式;
- 线程不多or业务规模大,程序整体启动会很缓慢的时候,用懒汉模式(延迟加载);
适配器模式
简介
- 适配器模式,说白了就是由于目标接口与已有接口不一致,但是大致功能相似,那么只要对已有接口稍作处理(进行继承or组合的嵌套封装),就能 适配 目标接口的调用方式。
举例
我们都知道每个国家的电器标准不同,那么如何在国外使用我们国内的双头标准交流电插头电器呢?
答案是配合一个交流电适配器使用!相当于对我们的双头标准交流电插头进行了一些处理,让它得意在别的国外接口上正常使用;
下面来一段抽象举例的代码,本段代码无实际业务意义,目的是让我们在代码层面理解适配器模式:
-
已有接口Target调用int参数类型;
-
新的接口调用string参数类型;
-
目的: 我们需要将调用string参数类型的接口Adaptee, 通过适配器Adaptor封装, 适配成调用int型参数的接口Target;
-
Clinet直接调用的是Target接口,我们提供了Adeptee接口,参数类型不一样,我们给Adeptee封装了个适配器Adapter,使其适配Client对Target的调用方法进行调用;
#include <iostream>
#include <memory>
#include <string>
class Target//目标接口
{
public:
virtual bool NewRequest(const int value)
{
std::cout << "Calling new request method!" << std::endl;
return true;
}
virtual ~Target(){}
};
class Adaptee//需要套适配器封装的接口
{
public:
bool OldRequest(const std::string& strValue)
{
std::cout << "Calling old request method!" << std::endl;
return true;
}
};
class Adaptor : public Target//适配器(继承方式)
{
private:
std::shared_ptr<Adaptee> m_smartAdaptee;
public:
Adaptor(std::shared_ptr<Adaptee> adaptee)//智能指针,好习惯
{
m_smartAdaptee = std::move(adaptee);
}
bool NewRequest(const int value)//
{
std::cout << "I'm new request method" << std::endl;
std::string strValue = std::to_string(value);//适配器内部使用了to_stinrg 将int型参数适配成了string,传入需要适配的接口进行调用!
m_smartAdaptee->OldRequest(strValue);
return true;
}
};
int main()
{
std::shared_ptr<Target> target(new Adaptor(std::make_shared<Adaptee>()));
target->NewRequest(10);
return 0;
}
小结
- 适配器模式一般用于软件开发后期,旧的软件接口已被大量使用,但是新的软件接口功能相同但是接口有差异,无法直接使用,这时如果重新统一接口设计,需要消耗大量的资源(时间、人力、物力),简单的使用一个适配器作为连接新旧接口的中转站就成为首选。
当然,业务中能严谨的统一接口更好,我们也就不需要出现问题再用适配器的形式解决问题了;(能预防为啥要等病发?)
迭代器模式
简介
提供一种顺序访问集合内元素的方法,但不暴露集合内部实现细节的设计模式,属于行为型设计模式.
迭代器模式将数据的存储和遍历相分离,存储交给聚合类,遍历交给迭代器类,如果需要对新的集合进行顺序遍历,只需要扩展新的相关迭代器类和相关聚合类。
简单来说,设计一种结构,提供给各种类 类似stl中迭代器一样的 在不暴露类内部存储结构情况下的 顺序遍历集合内元素的能力;
相关组成示例
我们以存储元素为Student类元素,代码编写一个迭代器模式学习
//学生类
//集合中存储的元素类型
class Student
{
public:
string _name; //姓名
string _stuNo; //学号
string _college; //学院
public:
Student(const string& name, const string& stuNo, const string& college)
:_name(name)
, _stuNo(stuNo)
, _college(college)
{}
//展示学生信息的函数,用于测试
void showInfo()
{
cout << "姓名: " << _name << "\t学号: " << _stuNo << "\t学院: " << _college << endl;
}
};
抽象聚合类
提供一个创建迭代器对象的接口、添加元素接口、删除元素接口
//抽象聚合类(基类,纯虚函数,由于后续派生类的扩展)
class Aggregate
{
public:
//添加学生信息
virtual void addStudent(const Student& stu) = 0;
//删除学生信息
virtual void deleteStudent(const Student& stu) = 0;
//创建迭代器
virtual Iterator* createIterator() = 0;
};
具体聚合类
根据存储元素方式的不同,重写创建迭代器接口,返回自己对应的迭代器
并重写添加元素和删除元素接口
具体聚合类和具体迭代器类是互相对应的,根据元素的存储方式,设计对应的遍历所用的迭代器;
//计算机学院聚合类
class ComputerAggregate : public Aggregate
{
private:
vector<Student> _info; //存储学生对象的集合(vector形式存储)
public:
//添加学生信息
virtual void addStudent(const Student& stu)
{
_info.emplace_back(stu);
}
//删除学生信息
virtual void deleteStudent(const Student& stu)
{
auto it = _info.begin();
while (it != _info.end())
{
if (it->_name == stu._name && it->_stuNo == stu._stuNo && it->_college == stu._college)
{
break;
}
++it;
}
_info.erase(it);
}
//创建具体迭代器函数
virtual Iterator* createIterator()
{
return new ComputerIterator(_info);
}
};
//体育学院聚合类
class SportAggregate : public Aggregate
{
private:
list<Student> _info; //存储学生对象的集合(list形式存储)
public:
//添加学生信息
virtual void addStudent(const Student& stu)
{
_info.push_back(stu);
}
//删除学生信息
virtual void deleteStudent(const Student& stu)
{
auto it = _info.begin();
while (it != _info.end())
{
if (it->_name == stu._name && it->_stuNo == stu._stuNo && it->_college == stu._college)
{
break;
}
++it;
}
_info.erase(it);
}
//创建具体迭代器函数
virtual Iterator* createIterator()
{
return new SportIterator(_info);
}
};
抽象迭代器类
该类提供可以查看集合中是否有下一个元素和走到集合的下一个元素位置的接口
//抽象迭代器类(基类,纯虚函数,由于后续派生类的扩展)
class Iterator
{
public:
//下一个位置是否有元素
virtual bool hasNext() = 0;
//返回当前元素,并且走到下一个位置
virtual Student next() = 0;
};
具体迭代器类
为具体聚合类实现对应的迭代器接口(因为不同聚合类内部存储元素的方式可能不同,比如vector和list这两种存储方式)
//用计算机学院 和 体育学院 两种派生具体迭代器举例
//1.计算机学院迭代器(抽象迭代器 根据聚合类存储形式的不同,派生的具体对应迭代器类)
class ComputerIterator : public Iterator
{
private:
vector<Student> _info; //迭代器访问的数据集合
int _curPos; //当前访问的下标位置
public:
ComputerIterator(const vector<Student>& info)
:_info(info)
, _curPos(0)
{}
//下一个位置是否有元素
virtual bool hasNext()
{
return _curPos < _info.size();
}
//返回当前元素,并且走到下一个位置
virtual Student next()
{
Student curStu = _info[_curPos++];
return curStu;
}
};
//2.体育学院迭代器(抽象迭代器 根据聚合类存储形式的不同,派生的具体对应迭代器类)
class SportIterator : public Iterator
{
private:
list<Student> _info; //迭代器访问的数据集合
public:
SportIterator(const list<Student>& info)
:_info(info)
{}
//下一个位置是否有元素
virtual bool hasNext()
{
return !_info.empty();
}
//返回当前元素,pop用过的元素,并且走到下一个位置(pop的目的是,list不支持随机访问,每次拿当前元素是list.front()这样的形式)
virtual Student next()
{
Student front = _info.front();
_info.pop_front();
return front;
}
};
测试代码
//计算机学院测试
void test_College()
{
Aggregate* computerCollege = new ComputerAggregate();
computerCollege->addStudent(Student("索隆", "11", "计算机"));
computerCollege->addStudent(Student("红发香克斯", "12", "计算机"));
computerCollege->addStudent(Student("路飞", "13", "计算机"));
computerCollege->addStudent(Student("娜美", "14", "计算机"));
computerCollege->addStudent(Student("山治", "15", "计算机"));
Iterator* it = computerCollege->createIterator();
cout << "************* 计算机学院 **************" << endl;
while (it->hasNext())
{
it->next().showInfo();
}
}
//体育学院测试
void test_Sport()
{
Aggregate* sportCollege = new SportAggregate();
sportCollege->addStudent(Student("白胡子", "0", "体育"));
sportCollege->addStudent(Student("雷利", "1", "体育"));
sportCollege->addStudent(Student("罗杰", "2", "体育"));
sportCollege->addStudent(Student("凯多", "3", "体育"));
sportCollege->addStudent(Student("黑胡子", "4", "体育"));
sportCollege->addStudent(Student("BigMom", "5", "体育"));
Iterator* it = sportCollege->createIterator();
cout << "************* 体育学院 **************" << endl;
while (it->hasNext())
{
it->next().showInfo();
}
}
运行结果
小结
优点:符合开闭原则(对 提供方 扩展开放,对 使用方 修改关闭),如果需要对新的元素集合进行遍历,只需要添加新的具体聚合类和具体迭代器类;
缺点:显而易见,需要添加新的具体聚合类和具体迭代器类进行扩展,类的数量会增多;
工厂模式
简单工厂模式
简单工厂模式(Simple Factory Pattern)专门定义一个 工厂类 来负责创建其他类的实例,被创建的实例通常具有共同的父类(简单工厂类)
简单工厂模式,是一种实例化对象的方式,只要 传入 需要实例化对象的名字,就可以通过相应工厂对象名字->来制造所需要的对象;
typedef enum Type
{
Apple,
Huawei,
}PRODUCTTYPE;
//抽象产品类 TV(电视机类)
class Phone
{
public:
virtual void Show() = 0;
virtual ~Phone() {};//声明析构函数为虚函数,防止内存泄漏
};
//具体产品类 Apple Phone(苹果手机类)
class ApplePhone : public Phone
{
public:
void Show()
{
cout << "I'm Apple phone " << endl;
}
};
//具体产品类 Huawei Phone(华为手机类)
class HuaweiPhone : public Phone
{
public:
void Show()
{
cout << "I'm Huawei Phone" << endl;
}
};
// 工厂类 PhoneFactory(手机工厂类)
class TVFactory
{
public:
Phone* CreatePhone(PRODUCTTYPE type)
{
switch (type)//通过传进来的type参数,来确定创建哪个类的实例;
{
case Apple:
return new ApplePhone();
case Huawei:
return new HuaweiPhone();
default:
return NULL;
}
}
};
int main()
{
// 创建工厂类对象
PhoneFactory* myPhoneFactory = new PhoneFactory();
Phone* applePhone = myPhoneFactory->CreatePhone(Apple); //这里的传参确定了创建哪种产品
if (applePhone != NULL)
applePhone->Show();
}
特征: 只有唯一的一个工厂类,该工厂负责所有种类产品的生产,是整个简单工厂模式的核心,提供了工厂方法CreatePhone()该方法中包含一个字符串类型的参数,在内部业务逻辑中根据参数值得不同实例化不同的具体产品类,返回相依的对象。
优点: 使用逻辑简单,大量new各种产品的时候,同意改变参数传入唯一工厂即可;
缺点:这种单工厂模式中最大的缺点是当有新产品要加入系统时,必须要修改工厂类,加入必要的处理逻辑,违背了“开闭原则”。其次是只有一个工厂,导致逻辑复杂,维护性差;
实际应用: 在程序中,需要创建的对象很多,导致对象的new操作多且杂时,需要使用简单工厂模式;
工厂方法模式
在工厂方法模式中,工厂父类负责定义创建产品对象的公告接口,而子工厂类负责生成具体的产品对象。目的是将产品的实例化操作延迟到子工厂类中完成,通过子工厂类来确定究竟应该实例化哪一个具体产品类。
/*抽象产品类 Phone(手机类)*/
class Phone
{
public:
virtual void Show() = 0;
virtual ~Phone();//声明析构函数为虚函数,防止内存泄漏
};
/*具体产品类 ApplePhone(苹果手机类)*/
class ApplePhone : public Phone
{
public:
void Show()
{
cout << "I'm Apple Phone" << endl;
}
};
/*具体产品类 HuaweiPhone(华为手机类)*/
class HuaweiPhone : public Phone
{
public:
void Show()
{
cout << "I'm Huawei Phone" << endl;
}
};
/*工厂类(电视机工厂类)*/
class PhoneFactory
{
public:
virtual Phone* CreatePhone() = 0;
virtual ~PhoneFactory() {};//析构函数声明为虚函数,防止内存泄漏
};
/*具体工厂类 ApplePhoneFactory(苹果手机工厂类)*/
class ApplePhoneFactory : public PhoneFactory
{
public:
Phone* CreatePhone()
{
return new ApplePhone();
}
};
/*具体工厂类 HuaweiPhoneFactory(华为手机工厂类)*/
class HuaweiPhoneFactory : public PhoneFactory
{
public:
Phone* CreatePhone()
{
return new HuaweiPhone();
}
};
int main(int argc, char* argv[])
{
PhoneFactory* applephonefactory = new ApplePhoneFactory();//实例化工厂抽象类
Phone* applePhone = applephonefactory->CreatePhone();//每种产品由每种子工厂创建
applePhone->Show();
PhoneFactory* huaweiphonefactory = new HuaweiPhoneFactory();
Phone* huaweiPhone = huaweiphonefactory->CreatePhone();//每种产品由每种子工厂创建
huaweiPhone->Show();
return 0;
}
特征: 每种产品都有对应的专门生产这种产品的子工厂;
优点:系统的扩展性好,符合“开闭原则” 。系统加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品即可。
缺点:在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体子工厂类,系统中类的个数将成对增加,一定程度上增加了系统的复杂度。
实际应用: 在设计的初期,就考虑到产品在后期会进行扩展的情况下,可以使用工厂方法模式;
抽象工厂模式
抽象工厂模式是工厂方法模式的泛化版,工厂方法模式是一种特殊的抽象工厂模式;
在工厂方法模式中,每个具体工厂只能生产一种具体的产品,如苹果手机厂只生产苹果手机,而抽象工厂方法模式中,一个具体的工厂可以生产多个具体产品(eg:苹果手机,苹果笔记本)。
// 抽象产品类类 Phone(手机类)
class Phone
{
public:
virtual void Show() = 0;
virtual ~Phone() {};//析构函数声明为虚函数,防止内存泄漏
};
//具体产品类 HaierTelevision(苹果手机类)
class ApplePhone : public Phone
{
public:
void Show()
{
cout << "I'm ApplePhone" << endl;
}
};
//具体产品类 HuaweiPhone(华为手机类)
class HuaweiPhone : public Phone
{
public:
void Show()
{
cout << "I'm HuaweiPhone" << endl;
}
};
// 抽象产品类 ComPuter(笔记本类)
class ComPuter
{
public:
virtual void Show() = 0;
virtual ~ComPuter() {};//析构函数声明为虚函数,防止内存泄漏
};
//具体产品类 AppleComPuter(苹果笔记本类)
class AppleComPuter : public ComPuter
{
public:
void Show()
{
cout << "I'm AppleComPuter" << endl;
}
};
//具体产品类 HuaweiComPuter(华为笔记本类)
class HuaweiComPuter : public ComPuter
{
public:
void Show()
{
cout << "I'm HuaweiComPuter" << endl;
}
};
// 抽象工厂类 EFactory(电器工厂类)
class EFactory
{
public:
virtual Phone* CreatePhone() = 0;
virtual ComPuter* CreateComPuter() = 0;
virtual ~EFactory() {};//析构函数声明为虚函数,防止内存泄漏
};
//具体工厂类 AppleFactory(苹果工厂类)
class AppleFactory : public EFactory
{
public:
Phone* CreatePhone()
{
return new ApplePhone();
}
ComPuter* CreateComPuter()
{
return new AppleComPuter();
}
};
//具体工厂类 HuaweiFactory(华为工厂类)
class HuaweiFactory : public EFactory
{
public:
Phone* CreatePhone()
{
return new ApplePhone();
}
ComPuter* CreateComPuter()
{
return new AppleComPuter();
}
};
int main(int argc, char* argv[])
{
EFactory* appleFactory = new AppleFactory();/*实例化工厂抽象基类*/
Phone* applePhone = appleFactory->CreatePhone();/*实例化产品抽象类*/
ComPuter* appleComPuter = appleFactory->CreateComPuter();
applePhone->Show();
appleComPuter->Show();
EFactory* huaweiFactory = new HuaweiFactory();
Phone* huaweiPhone = huaweiFactory->CreatePhone();
ComPuter* huaweiComPuter = huaweiFactory->CreateComPuter();
huaweiPhone->Show();
huaweiComPuter->Show();
}
特征:在一个抽象工厂中可以定义一组多种方法,每一个方法对应一个产品种类
优点:1. 抽象工厂模式将产品族的依赖与约束关系放到抽象工厂中,便于管理。2. 切换产品族容易,只需要增加一个具体工厂实现,客户端选择另一个套餐就可以了
缺点: 1.比较繁琐: 抽象工厂模式类增加的速度很快**,有一个产品族就需要增加一个具体工厂实现**; 2.产品族难以扩展产品: 当产品族中增加一个产品时,抽象工厂接口中需要增加一个函数,对应的所有具体工厂实现都需要修改,修改放大严重。
实际应用:当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。比如apple的手机和电脑都属于apple产品族;
小结
简单工厂模式:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。(方便操作,但是扩展性差)
工厂方法模式:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。(方便扩展,但是子工厂的数量会增多,复杂度变高)
抽象工厂模式:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了子工厂类的数量。(相比于工厂方法模式,引入了族产品的概念,进而减少了子工厂类的数量;缺点如同工厂方法模式,产品族变多,子工厂类还是会变多,变复杂而操作繁琐)
参考文章