设计模式概述
设计模式
设计模式主要研究的是“变”与“不变”,以及如何将它们分离、解耦、组装,将其中“不变”的部分沉淀下来,避免“重复造轮子”,而对于“变”的部分则可以用抽象化、多态化等方式,增强软件的兼容性、可扩展性。如果将编写代码比喻成建筑施工,那么设计模式就像是建筑设计。这就像乐高积木的设计理念一样,圆形点阵式的接口具有极强的兼容性,能够让任意组件自由拼装、组合,形成一个全新的物件。
设计模式 | |
创建型设计模式 | 单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式 |
结构型设计模式 | 门面模式(外观模式)、组合模式、装饰器模式、适配器模式、享元模式、代理模式、桥接模式 |
行为型设计模式 | 模板方法模式、迭代器模式、责任链模式、策略模式、状态模式、备忘录模式、中介模式、命令模式、访问者模式、观察者模式、解释器模式 |
设计原则
1.单一职责原则 SRP
2.开闭原则 OCP
3.里氏替换原则 LSP
4.接口隔离原则 ISP
5.依赖倒置原则 DIP
6.迪米特法则
1 创建型设计模式
1.1 单例模式(Singleton)
顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。
单例的构造方法用private关键字隐藏起来,对外只提供getInstance()方法以获得这个单例对象。用关键字static确保单例的静态性,将单例放入内存静态区,在类加载的时候就初始化了。它与类同在,也就是说它是与类同时期且早于内存堆中的对象实例化的,该实例在内存中永生。
/* 懒汉模式:即在初始阶段就主动进行实例化,无论此单例是否有人使用 */
class Singleton
{
public:
static Singleton& getInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return *instance;
}
// 防止拷贝构造函数和赋值运算符被调用
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton(){}
static Singleton *instance;
};
/* 饿汉模式:在使用到的时候才去初始化对象 */
class Singleton
{
public:
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}
// 防止拷贝构造函数和赋值运算符被调用
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton(){}
}
相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。
1.2 原型模式(Prototype)
原型模式达到以原型实例创建副本实例的目的即可,并不需要知道其原始类,也就是说,原型模式可以用对象创建对象,而不是用类创建对象,以此达到效率的提升。对于那些有非常复杂的初始化过程的对象或者是需要耗费大量资源的情况,原型模式是更好的选择。
1.3 工厂方法模式(Factory Method)
工厂方法模式对工厂制造方法进行接口规范化,以允许子类工厂决定具体制造哪类产品的实例,最终降低系统耦合,使系统的可维护性、可扩展性等得到提升。
#include "helloworld.h"
#include <iostream>
using namespace std;
class Product
{
public:
virtual void show() const = 0;
};
class ProductA : public Product
{
public:
void show() const {cout<<"This is Product A"<<endl;}
};
class ProductB : public Product
{
public:
void show() const {cout<<"This is Product B"<<endl;}
};
/* 工厂方法模式 */
class Factory
{
public:
virtual Product* createProduct() const = 0;
};
class FactoryA : public Factory
{
public:
Product* createProduct() const {return new ProductA();}
};
class FactoryB : public Factory
{
public:
Product* createProduct() const {return new ProductB();};
};
/* 简单工厂模式 */
class SimpleFactory
{
public:
static Product* createProduct(const string type)
{
if("A"==type)
{
return new ProductA();
}
else if("B"==type)
{
return new ProductB();
}
else
{
return nullptr;
}
}
};
int main()
{
cout<<"Factory Method"<<endl;
FactoryA fa;
Product *pa = fa.createProduct();
pa->show();
delete pa;
FactoryB fb;
Product *pb = fb.createProduct();
pb->show();
delete pb;
cout<<"Simple Factory"<<endl;
Product *a = SimpleFactory::createProduct("A");
if (a)
{
a->show();
delete a;
}
Product *b = SimpleFactory::createProduct("B");
if (b)
{
a->show();
delete b;
}
return 0;
}
/*
运行结果:
Factory Method
This is Product A
This is Product B
Simple Factory
This is Product A
This is Product B
*/
1.4 抽象工厂模式(Abstract Factory)
抽象工厂模式是对工厂的抽象化,而不只是制造方法。为了满足不同用户对产品的多样化需求,工厂不会只局限于生产一类产品,但是系统如果按工厂方法那样为每种产品都增加一个新工厂又会造成工厂泛滥。所以,为了调和这种矛盾,抽象工厂模式提供了另一种思路,将各种产品分门别类,基于此来规划各种工厂的制造接口,最终确立产品制造的顶级规范,使其与具体产品彻底脱钩。抽象工厂是建立在制造复杂产品体系需求基础之上的一种设计模式,在某种意义上,我们可以将抽象工厂模式理解为工厂方法模式的高度集群化升级版。
#include <iostream>
using namespace std;
class Phone
{
public:
virtual void show() const = 0;
};
class MIPhone : public Phone
{
public:
void show() const {cout<<"This is a Mi Phone."<<endl;}
};
class HonorPhone : public Phone
{
public:
void show() const {cout<<"This is a Honor Phone."<<endl;}
};
class Computer
{
public:
virtual void show() const = 0;
};
class MIComputer : public Computer
{
public:
void show() const {cout<<"This is a Mi Computer."<<endl;}
};
class HonorComputer : public Computer
{
public:
void show() const {cout<<"This is a Honor Computer."<<endl;}
};
class AbstractFactory
{
public:
virtual Phone* createPhone() const = 0;
virtual Computer* createComputer() const = 0;
};
class MiFactory : public AbstractFactory
{
public:
Phone* createPhone() const {return new MIPhone();}
Computer* createComputer() const {return new MIComputer();}
};
class HonorFactory : public AbstractFactory
{
public:
Phone* createPhone() const {return new HonorPhone();}
Computer* createComputer() const {return new HonorComputer();}
};
int main()
{
cout<<"Abstract Factory:"<<endl;
MiFactory miF;
Phone* miP = miF.createPhone();
Computer* miC = miF.createComputer();
miP->show();
miC->show();
HonorFactory hoF;
Phone* hoP = hoF.createPhone();
Computer* hoC = hoF.createComputer();
hoP->show();
hoC->show();
delete miP;
delete miC;
delete hoP;
delete hoC;
return 0;
}
/*
运行结果:
Abstract Factory:
This is a Mi Phone.
This is a Mi Computer.
This is a Honor Phone.
This is a Honor Computer.
*/
工厂方法模式和抽象工厂模式都是创建型设计模式,它们的主要区别在于创建方法:
首先,工厂方法模式关注的是单个产品对象的创建,它定义了一个用于创建产品的接口,但将实际的产品创建过程推迟到具体工厂类的子类中进行。这意味着工厂方法模式适用于生产同一产品线的不同系列产品。而抽象工厂模式则关注的是整个产品族的创建,它提供了一系列用于创建相关或相互依赖的产品对象的方法,这样可以实现更高级的对象组合。当一个产品族只有两个产品时,工厂方法模式适用;当一个系统需要支持多个产品族时,抽象工厂模式就派上用场了。
其次,抽象工厂模式要求所有的产品族必须使用同一个工厂来创建,这意味着所有的产品都必须由相同的接口来描述。而工厂方法模式并不需要这样做,每个具体工厂可以有自己的实现方式。
总的来说,简单工厂模式、工厂方法模式以及抽象工厂模式是逐步抽象并更具一般性的设计模式。简单工厂模式是超级工厂,可以生产无关联的各种产品;工厂方法模式针对的是同一产品线的不同系列产品的创建;而抽象工厂模式则是对多个产品族的创建进行了抽象。
1.5 建造者模式
建造者模式又称为生成器模式,主要用于对复杂对象的构建、初始化,它可以将多个简单的组件对象按顺序一步步组装起来,最终构建成一个复杂的成品对象。建造者的制造过程不仅要分步完成,还要按照顺序进行,所以建造者的各制造步骤与逻辑都应该被抽离出来独立于数据模型。
与工厂系列模式不同的是,建造者模式的主要目的在于把烦琐的构建过程从不同对象中抽离出来,使其脱离并独立于产品类与工厂类,最终实现用同一套标准的制造工序能够产出不同的产品。
建造者模式的各角色:
产品(Product)、建造者(Builder)、建造者实现(ConcreteBuilder)、指导者(Director)
2 结构型设计模式
2.1 门面模式(Facade,也叫外观模式)
门面模式将多个不同的子系统接口封装起来,对外提供统一的高层接口,使复杂的子系统变得更易使用。
2.2 组合模式(Composite)
组合模式(Composite)是针对由多个节点对象(部分)组成的树形结构的对象(整体)而发展出的一种结构型设计模式,它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象使用方法的一致性,使客户端不必关注对象的整体或部分,最终达到对象复杂的层次结构与客户端解耦的目的。
最常见的有叉树结构和文件系统。
2.3 装饰器模式(Decorator)
装饰器模式(Decorator)能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能。区别在于方式的不同,继承是在编译时(compile-time)静态地通过对原始类的继承完成,而装饰器模式则是在程序运行时(run-time)通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。
2.4 适配器模式(Adapter)
比如:两相插头使用三相的插孔。
目标接口(Traget):三相的插孔
适配器(Adapter):继承自被适配者类且实现了目标接口,负责适配被适配者接口为目标接口。
被适配者(Adaptee):两相插头
2.5 享元(Flyweight)
当系统存在大量的对象,并且这些对象又具有相同的内部状态时,我们就可以用享元模式共享相同的元件对象,以避免对象泛滥造成资源浪费。
2.6 代理模式(Proxy)
代理模式(Proxy),顾名思义,有代表打理的意思。某些情况下,当客户端不能或不适合直接访问目标业务对象时,业务对象可以通过代理把自己的业务托管起来,使客户端间接地通过代理进行业务访问。如此不但能方便用户使用,还能对客户端的访问进行一定的控制。简单来说,就是代理方以业务对象的名义,代理了它的业务。
代理模式与装饰器模式二者的理念与实现有点类似,但装饰器模式往往更加关注为其他对象增加功能,让客户端更加灵活地进行组件搭配;而代理模式更强调的则是一种对访问的管控,甚至是将被代理对象完全封装而隐藏起来,使其对客户端完全透明。我们大可不必被概念所束缚,属于哪种模式并不重要,最适合系统需求的设计就是最好的设计。
代理模式不仅能增强原业务功能,更重要的是还能对其进行业务管控。对用户来讲,隐藏于代理中的实际业务被透明化了,而暴露出来的是代理业务,以此避免客户端直接进行业务访问所带来的安全隐患,从而保证系统业务的可控性、安全性。
2.7桥接模式(Bridge)
桥接模式(Bridge)能将抽象与实现分离,使二者可以各自单独变化而不受对方约束,使用时再将它们组合起来,就像架设桥梁一样连接它们的功能,如此降低了抽象与实现这两个可变维度的耦合度,以保证系统的可扩展性。
3 行为型设计模式
3.1模板方法(Template Method)
模板方法模式(Template Method)则是对一系列类行为(方法)的模式化。
3.2迭代器模式(Iterator)
迭代器模式(Iterator)提供了一种机制来按顺序访问集合中的各元素,而不需要知道集合内部的构造。
3.3责任链模式(Chain of Responsibility)
责任链是由很多责任节点串联起来的一条任务链条,其中每一个责任节点都是一个业务处理环节。责任链模式(Chain of Responsibility)允许业务请求者将责任链视为一个整体并对其发起请求,而不必关心链条内部具体的业务逻辑与流程走向,也就是说,请求者不必关心具体是哪个节点起了作用,总之业务最终能得到相应的处理。在软件系统中,当一个业务需要经历一系列业务对象去处理时,我们可以把这些业务对象串联起来成为一条业务责任链,请求者可以直接通过访问业务责任链来完成业务的处理,最终实现请求者与响应者的解耦。
责任链模式的本质是处理某种连续的工作流,并确保业务能够被传递至相应的责任节点上得到处理。
3.4 策略模式(Strategy)
策略模式(Strategy)强调的是行为的灵活切换,比如电脑的USB接口。
3.5 状态模式(State)
状态模式(State)构架出一套完备的事物内部状态转换机制,并将内部状态包裹起来且对外部不可见,使其行为能随其状态的改变而改变,同时简化了事物的复杂的状态变化逻辑。
状态模式的应用将系统状态从系统环境(系统宿主)中彻底抽离出来,状态接口确立了高层统一规范,使状态响应机制分立、自治,以一种松耦合的方式实现了系统状态与行为的联动机制。
从类结构上看,状态模式与策略模式非常类似,其不同之处在于,策略模式是将策略算法抽离出来并由外部注入,从而引发不同的系统行为,其可扩展性更好;而状态模式则将状态及其行为响应机制抽离出来,这能让系统状态与行为响应有更好的逻辑控制能力,并且实现系统状态主动式的自我转换。状态模式与策略模式的侧重点不同,所以适用于不同的场景。总之,如果系统中堆积着大量的状态判断语句,那么就可以考虑应用状态模式,它能让系统原本复杂的状态响应及维护逻辑变得异常简单。状态的解耦与分立让代码看起来更加清晰、明了,可读性大大增强,同时系统的运行效率与健壮性也能得到全面提升。
3.6 备忘录模式(Memento)
备忘录模式(Memento)则可以在不破坏元对象封装性的前提下捕获其在某些时刻的内部状态,并像历史快照一样将它们保留在元对象之外,以备恢复之用。
3.7 中介模式(Mediator)
中介模式(Mediator)为对象构架出一个互动平台,通过减少对象间的依赖程度以达到解耦的目的。
3.8 命令模式(Command)
命令模式(Command)能够将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦,双方只通过传递各种命令过象来完成任务。
命令模式能使我们在不改变任何现有系统代码的情况下,实现命令功能的无限扩展。
命令模式巧妙地利用了命令接口将命令请求方与命令执行方隔离开来,使发号施令者与任务执行者解耦,甚至意识不到对方接口的存在而全靠命令的上传下达。
命令模式其实与策略模式非常类似,只不过前者较后者多了一层封装,命令接口的统一确立,使系统可以忽略命令执行方接口的多样性与复杂性,将接口对接与业务逻辑交给具体的命令去实现,并且实现命令的无限扩展。
3.9 访问者模式(Visitor)
访问者模式(Visitor)主要解决的是数据与算法的耦合问题,尤其是在数据结构比较稳定,而算法多变的情况下。为了不“污染”数据本身,访问者模式会将多种算法独立归类,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并且确保算法的自由扩展。
3.10 观察者模式(Observer)
观察者模式(Observer)可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。
3.11 解释器模式(Interpreter)
4 设计原则
1.单一职责原则 SRP
2.开闭原则 OCP
3.里氏替换原则 LSP
4.接口隔离原则 ISP
5.依赖倒置原则 DIP
6.迪米特法则