设计模式可分为三大类:
- 创建型模式 (Creational Patterns)
- 结构性模式 (Structural Patterns)
- 行为型模式 (Behavioral Patterns)
模式描述 | 包括 |
---|---|
创建型模式 | 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
结构型模式 | 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
行为型模式 | 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
我们将详细阐述一下三大类其中比较常用的设计模式,并简要分析一下利弊。
文章目录
创建型模式
工厂模式
什么是工厂模式呢?首先我们来看一下工厂模式的定义:
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
-
该模式的动机在于:
定义一个创建对象的接口,通过不同的参数传递,让其子类决定具体创建哪一类型的实例,工厂模式让对象的创建过程延迟到了子类过程创建。对用户来讲,不用关心对象是怎么创建的,只需要知道不同的参数条件便可触发创建不同的实例即可。 -
应用场景:
1.日志记录器:对于用户来讲,日志可以保存到本地硬盘、系统事件、远程服务器,而为了将日志记录器和系统的其他功能解耦合,故可用日志记录器,只暴露一个接口传递参数,让工厂类创建实例即可。
2.数据库访问:对于用户来讲,用户可能不知道系统最终会选择什么数据库,以及数据库可能产生变化时,故可使用工厂模式,隐藏数据库创建过程,暴漏接口供系统调用。
3.设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
可以拿富士康工厂来举例。
简单工厂模式(Simple Factory Pattern)
-
工厂说明
在富士康中会生产很多类型的手机,比如苹果、三星、诺基亚等等牌子的手机,工厂可随意指定生产线生产某一个品牌的手机。 -
UML图
-
结构组成
模式结构包括如下:- Factory:工厂角色
工厂角色负责实现创建所有实例的内部逻辑 - Product:抽象产品角色
抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口 - ConcreteProduct:具体产品角色
具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
- Factory:工厂角色
-
简单工厂的缺点
扩展性非常差,当需要添加新的产品时,需要更改工厂类,违背了开放-封闭原则(软件实体(类、模块、函数等等)应该可以扩展,但是不可以修改。) -
简单工厂代码
Phone()为抽象类,用于子类的共同函数,开放接口为show(),用于显示品牌信息。
ApplePhone()、SamsungPhone()、NokiaPhone()为子类,具体类实现过程在此类中实现。
#include <iostream>
#include <string>
class Phone{
public:
virtual ~Phone() {}
virtual void show() = 0;
};
class ApplePhone : public Phone {
public:
void show() {
std::cout << "ApplePhone created" << std::endl;
}
};
class SamsungPhone : public Phone {
public:
void show() {
std::cout << "SamsungPhone created" << std::endl;
}
};
class NokiaPhone : public Phone {
public:
void show() {
std::cout << "NokiaPhone created" << std::endl;
}
};
enum PhoneType
{
APPLE,
SAMSUNG,
NOKIA
};
class PhoneFactory {
public:
Phone* createPhone(PhoneType phone_type) {
switch (phone_type)
{
case APPLE:
return new ApplePhone();
case SAMSUNG:
return new SamsungPhone();
case NOKIA:
return new NokiaPhone();
default:
return nullptr;
}
}
};
int main()
{
PhoneFactory phoneFactory;
Phone* pApplePhone = phoneFactory.createPhone(APPLE);
if (pApplePhone) {
pApplePhone->show();
delete pApplePhone;
pApplePhone = nullptr;
}
return 0;
}
运行结果:
ApplePhone created
工厂方法模式(Factory Method Pattern)
- 模式动机
现在对该系统进行修改,不再设计一个工厂类来统一负责所有产品的创建,而是将具体手机的创建过程交给专门的工厂子类去完成,我们先定义一个抽象的手机工厂类,再定义具体的工厂类来生成苹果手机、三星手机、诺基亚手机等,它们实现在抽象手机工厂类中定义的方法。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的手机类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新牌子手机的实例,这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”。(大白话:现在工厂专门为每一个品牌设立了子工厂,由子工厂负责具体产品的生产)
工厂方法模式和简单工厂模式的区别在于,工厂方法将创建具体实例的过程封装到了具体工厂类中,由具体的工厂类来实现某一个产品的创建。当有新的产品需要创建时,只需添加新的工厂类即可。
-
模式定义
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。 -
UML结构图
-
模式结构:
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
-
优点:
- 用户只需关注具体某一种产品的实现,对其他产品的实例化过程进行隐藏。
- 所有具体类都共用同一个抽象类
- 当有新的产品需求时,只需增加新的具体产品类和新的具体工厂类,不用对抽象类进行删改。满足了开放-封闭原则
-
缺点:
- 在增加新的产品时,需要同时增加具体产品类和具体工厂类,增加了系统的编译负担。
- 在创建实例时,需要将抽象类暴漏给用户,因为有太多抽象类,不容易让用户理解。
-
工厂方法模式代码
#include <iostream>
#include <string>
class Phone {
public:
virtual ~Phone() {}
virtual void show() = 0;
};
class ApplePhone : public Phone {
public:
void show() {
std::cout << "ApplePhone created" << std::endl;
}
};
class SamsungPhone : public Phone {
public:
void show() {
std::cout << "SamsungPhone created" << std::endl;
}
};
class NokiaPhone : public Phone {
public:
void show() {
std::cout << "NokiaPhone created" << std::endl;
}
};
class PhoneFactory {
public:
virtual ~PhoneFactory() {}
virtual Phone* createPhone() = 0;
};
class AppleFactory : public PhoneFactory{
public:
Phone* createPhone() {
return new ApplePhone();
}
};
class SamsungFactory : public PhoneFactory {
public:
Phone* createPhone() {
return new SamsungPhone();
}
};
class NokiaFactory : public PhoneFactory {
public:
Phone* createPhone() {
return new NokiaPhone();
}
};
int main()
{
PhoneFactory* pPhoneFactory = new AppleFactory();
Phone* pApplePhone = pPhoneFactory->createPhone();
pApplePhone->show();
delete pApplePhone;
delete pPhoneFactory;
return 0;
}
抽象工厂模式(Abstract Factory Pattern)
-
模式动机
在工厂方法中,子工厂只有一条手机生产线。但在抽象工厂模式中子工厂中不止要生产手机,还要生产平板,电脑等产品。 -
UML结构图
-
模式组成
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品 -
抽象工厂的优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 -
抽象工厂的缺点:
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。 -
模式代码实例
#include <iostream>
#include <string>
class Phone {
public:
virtual ~Phone() {}
virtual void show() = 0;
};
class Pad {
public:
virtual ~Pad(){}
virtual void show() = 0;
};
class ApplePhone : public Phone {
public:
void show() {
std::cout << "ApplePhone created" << std::endl;
}
};
class SamsungPhone : public Phone {
public:
void show() {
std::cout << "SamsungPhone created" << std::endl;
}
};
class NokiaPhone : public Phone {
public:
void show() {
std::cout << "NokiaPhone created" << std::endl;
}
};
class ApplePad : public Pad {
public:
void show() {
std::cout << "ApplePad created" << std::endl;
}
};
class SamsungPad : public Pad {
void show() {
std::cout << "SamsungPad created" << std::endl;
}
};
class NokiaPad : public Pad {
void show() {
std::cout << "NokiaPad created" << std::endl;
}
};
class Factory {
public:
virtual ~Factory() {}
virtual Phone* createPhone() = 0;
virtual Pad* createPad() = 0;
};
class AppleFactory : public Factory {
public:
Phone* createPhone() {
return new ApplePhone();
}
Pad* createPad() {
return new ApplePad();
}
};
class SamsungFactory : public Factory {
public:
Phone* createPhone() {
return new SamsungPhone();
}
Pad* createPad() {
return new SamsungPad();
}
};
class NokiaFactory : public Factory {
public:
Phone* createPhone() {
return new NokiaPhone();
}
Pad* createPad() {
return new NokiaPad();
}
};
int main()
{
Factory* pPhoneFactory = new AppleFactory();
Phone* pApplePhone = pPhoneFactory->createPhone();
Pad* pApplePad = pPhoneFactory->createPad();
pApplePhone->show();
pApplePad->show();
delete pApplePad;
delete pApplePhone;
delete pPhoneFactory;
return 0;
}
单例模式(Singleton Pattern)
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。
在单例模式中,一个类只有一个实例,并且该实例在整个应用程序中都可以被访问。这样可以确保系统中某些资源的全局唯一性,并且可以提供一个方便的访问点来访问该实例。
通常情况下,单例模式通过私有构造函数和静态方法来实现。私有构造函数用于防止在外部创建该类的实例,而静态方法则用于返回该类唯一的实例。
单例模式有多种实现方式,如懒汉式、饿汉式、双重检查锁定等,每种实现方式都有其特点和适用场景。
- 使用场景
-
资源共享:有些对象需要占用系统资源,如数据库连接池、线程池、日志对象等,这些对象需要全局唯一,以避免资源竞争和浪费。
-
配置信息:有些配置信息只需要被加载一次,如系统配置信息、数据库配置信息等,这些配置信息也需要全局唯一,以避免重复加载和浪费。
-
日志记录:有些应用需要记录系统运行日志,如应用程序启动日志、错误日志等,这些日志对象需要全局唯一,以确保日志记录的一致性和完整性。
-
状态缓存:有些应用需要缓存一些状态信息,如用户登录状态、购物车状态等,这些状态信息需要全局唯一,以确保状态的一致性和完整性。
-
- 懒汉式单例模式
在系统运行时,实例不会自动创建,而是等到使用到该类的时候才会创建一个实例对象。
优点:可以延迟实例的创建时间,从而节省系统资源。
缺点:线程是不安全的,当存在多个线程的时候,会创建多个实例。
#include<iostream>
#include<mutex>
using namespace std;
class Singleton {
public:
static Singleton* Getinstance()
{
if (instance == nullptr)
{
instance = new Singleton;
}
return instance;
}
private:
Singleton() {}
static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;
加了锁之后的懒汉模式:
#include<iostream>
#include<mutex>
using namespace std;
mutex my_mutex;
class Singleton {
public:
static Singleton* Getinstance()
{
if (instance == nullptr)
{
unique_lock<std::mutex> urgard(my_mutex);
if (instance == nullptr)
{
static CGrhuishou huishou;
instance = new Singleton;
}
return instance;
}
}
class CGrhuishou
{
public:
~CGrhuishou()
{
if (Singleton::instance)
{
delete Singleton::instance;
Singleton::instance = nullptr;
}
}
};
private:
Singleton() {}
static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;
int main()
{
}
另外一种线程安全的风格:
#include <atomic>
#include <memory>
class Widget {
public:
static Widget* getInstance() {
Widget* tmp = instance.load(); // 加载当前实例指针
if (tmp == nullptr) { // 如果为空,则尝试创建新实例
tmp = new Widget;
if (instance.compare_exchange_strong(nullptr, tmp)) { // 将新实例赋值给原子指针,并检查是否成功
return tmp; // 成功则返回新实例指针
} else {
delete tmp; // 失败则删除新实例,并返回已存在的实例指针
return instance.load();
}
} else {
return tmp; // 如果不为空,则直接返回当前实例指针
}
}
private:
static std::atomic<Widget*> instance; // 原子指针存储唯一实例
Widget() {} // 私有构造函数防止外部创建
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
};
std::atomic<Widget*> Widget::instance(nullptr);
- 饿汉式单例模式
在一开始类加载的时候就已经在实例化,并且在创建单例对象,以后只管用即可。
#include<iostream>
using namespace std;
class Singleton {
public:
static Singleton* Getinstance()
{
return instance;
}
private:
Singleton(){}
static Singleton* instance;
};
Singleton* Singleton::instance = new Singleton;
int main()
{
}