【一文快速理解23种设计模式】

        软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。这就不得不提到面向对象OO。

        面向对象OO = 面向对象的分析OOA + 面向对象的设计OOD + 面向对象的编程OOP

        面向对象的优势是易于软件的维护和功能的增减、可重用性好,提升开发效率、提高人机界面的开发性。万物皆对象,不同于面向过程,我们以现实生活的思维方式去考虑。



前言

设计模式中运用了面向对象编程语言的重要特性: 封装、继承、多态  <= 可以参考

虽然Java语言在面对对象和多线程特性上比其他语言更显纯粹,但C++的指针用好了就是爽!!!

一、模式简介

  • GoF的23种设计模式的功能

前面说明了 GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍。

  1. 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

  2. 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

  3. 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。

  4. 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

  5. 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

  6. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

  7. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

  8. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

  9. 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。

  10. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  11. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

  12. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

  13. 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

  14. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

  15. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

  16. 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

  17. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。

  18. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

  19. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

  20. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

  21. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

  22. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

  23. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

二、实际使用

 

1. 单例(Singleton)模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 主要解决:一个全局使用的类频繁地创建与销毁。 何时使用:想控制实例数目,节省系统资源的时候。 如何解决:判断系统是否已存在单例,如果有则返回,没有则创建。 关键代码:构造函数是私有的。

单例大约有两种实现方法:懒汉与饿汉。 懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现; 饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择: 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。 在访问量较小时,采用懒汉实现。这是以时间换空间。

//懒汉式一般实现:非线程安全,getInstance返回的实例指针需要delete
class Singleton
{
public:
    static Singleton* getInstance();
    ~Singleton(){}

private:
    static Singleton* m_pSingleton;
    Singleton(){}    
    Singleton(const Singleton& obj) = delete;  //明确拒绝
    Singleton& operator=(const Singleton& obj) = delete; //明确拒绝
};

Singleton* Singleton::m_pSingleton = NULL;

Singleton* Singleton::getInstance()
{
    if(m_pSingleton == NULL)
    {
        m_pSingleton = new Singleton;
    }
    return m_pSingleton;
}
//END

//懒汉式:加lock,线程安全
std::mutex mt;

class Singleton
{
public:
    static Singleton* getInstance();
private:
    Singleton(){}
    Singleton(const Singleton&) = delete;  //明确拒绝
    Singleton& operator=(const Singleton&) = delete; //明确拒绝

    static Singleton* m_pSingleton;

};
Singleton* Singleton::m_pSingleton = NULL;

Singleton* Singleton::getInstance()
{
    if(m_pSingleton == NULL)
    {
        mt.lock();
        m_pSingleton = new Singleton();
        mt.unlock();
    }
    return m_pSingleton;
}
//END

//返回一个reference指向local static对象
//多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。
class Singleton
{
public:
    static Singleton& getInstance();
private:
    Singleton(){}
    Singleton(const Singleton&) = delete;  //明确拒绝
    Singleton& operator=(const Singleton&) = delete; //明确拒绝
};


Singleton& Singleton::getInstance()
{
    static Singleton singleton;
    return singleton;
}
//END

//饿汉式:线程安全,注意delete
class Singleton
{
public:
    static Singleton* getInstance();
private:
    Singleton(){}
    Singleton(const Singleton&) = delete;  //明确拒绝
    Singleton& operator=(const Singleton&) = delete; //明确拒绝

    static Singleton* m_pSingleton;
};

Singleton* Singleton::m_pSingleton = new Singleton();

Singleton* Singleton::getInstance()
{
    return m_pSingleton;
}
//END

2. 原型(Prototype)模式 

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 主要解决:在运行期建立和删除对象。 何时使用: 1) 当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些; 2) 有的时候,我们需要一个对象在某个状态下的副本,此时,我们使用原型模式是最好的选择;例如:一个对象,经过一段处理之后,其内部的状态发生了变化;这个时候,我们需要一个这个状态的副本,如果直接new一个新的对象的话,但是它的状态是不对的,此时,可以使用原型模式,将原来的对象拷贝一个出来,这个对象就和之前的对象是完全一致的了; 3) 当我们处理一些比较简单的对象时,并且对象之间的区别很小,可能就几个属性不同而已,那么就可以使用原型模式来完成,省去了创建对象时的麻烦了; 4) 有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。

适当的时候考虑一下原型模式,能减少对应的工作量,减少程序的复杂度,提高效率

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。 关键代码:拷贝,return new className(*this);

浅拷贝:就是给对象中的每个成员变量进行复制,就是把A1类中的变量直接赋给A2类中变量,属于值传递,但是涉及到有new之类内存分配的地方,他们却是共享内存的。

深拷贝:就是不仅使用值传递,而是要每个变量都有自己一份独立的内存空间,互不干扰。

默认的拷贝构造函数是浅拷贝的,如果要实现深拷贝,就需要重写拷贝构造函数T(const T&)。

既然有了拷贝构造函数,还要引入原型模式呢?根据我自己查阅资料之后,我觉得好像是面向对象语言中都是引用传递,而且只提供简单的浅拷贝,所以没有拷贝构造函数这么一说,于是要实现深拷贝的功能,就需要原型模式。

class Clone
{
public:
    Clone()
    {
    }
    virtual ~Clone()
    {
    }
    virtual Clone* clone() = 0;
    virtual void show() = 0;
};

class Sheep: public Clone
{
public:
    Sheep(int id, string name): Clone(),m_id(id),m_name(name)
    {
        cout << "Sheep() id add:" << &m_id << endl;
        cout << "Sheep() name add:" << &m_name << endl;
    }
    ~Sheep()
    {
    }

    Sheep(const Sheep& obj)
    {
        this->m_id = obj.m_id;
        this->m_name = obj.m_name;
        cout << "Sheep(const Sheep& obj) id add:" << &m_id << endl;
        cout << "Sheep(const Sheep& obj) name add:" << &m_name << endl;
    }
     
    Clone* clone()
    {
        return new Sheep(*this);
    }
    void show()
    {
        cout << "id  :" << m_id << endl;
        cout << "name:" << m_name.data() << endl;
    }
private:
    int m_id;
    string m_name;
};

int main()
{
    Clone* s1 = new Sheep(1, "abs");
    s1->show();
    Clone* s2 = s1->clone();
    s2->show();
    delete s1;
    delete s2;
    return 0;
}

13. 模板方法(TemplateMethod)模式

        模板模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要解决:多个子类有相同的方法,并且逻辑相同,细节有差异;

如何解决:对于重要,复杂的算法,将核心算法设计为模板方法,周边细节由子类实现,重构时经常使用的方法,将相同的代码抽象到父类,通过钩子函数约束行为

关键代码:在抽象类实现通用接口,细节变化在子类实现

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

例子:我们在上电启动计算机时,硬件的加载顺序是一样的。先启动CPU,然后启动内存RAM,然后启动图形化人机界面。但是不同的价格和厂家的计算机其 CPU RAM 图形界面具体启动实现也是不同的,其中的具体实现将作为细节在子类中完成,父类仅作为主要流程的实现。代码如下:

class Computer
{
public:
    void product() //抽象的算法流程
    {
        installCpu();
        installRam();
        installGraphicsCard();
    }

protected:
    virtual void installCpu() = 0;
    virtual void installRam() = 0;
    virtual void installGraphicsCard() = 0;
};

class ComputerA: public Computer
{
protected:
    void installCpu() override
    {
        cout << "ComputerA install Inter Core i5" << endl;
    }

    void installRam() override
    {
        cout << "ComputerA install 2G Ram" << endl;
    }
     
    void installGraphicsCard() override
    {
        cout << "ComputerA install Gtx940 GraphicsCard" << endl;
    }
};

class ComputerB: public Computer
{
protected:
    void installCpu() override
    {
        cout << "ComputerB install Inter Core i7" << endl;
    }

    void installRam() override
    {
        cout << "ComputerB install 4G Ram" << endl;
    }
     
    void installGraphicsCard() override
    {
        cout << "ComputerB install Gtx960 GraphicsCard" << endl;
    }
};

 

18. 观察者(Observer)模式

        观察者模式关键对象是目标(subject)和观察者(observer),也叫做发布-订阅(publish-subscrible)模式:定义对象间的一种一对多的依赖关系(通常是 '一对多',有时候也是 '多对一' 或 '多对多' 的关系),即当一个主体对象的状态发生改变时,所有依赖于它的观察者对象都会得到通知并自动更新。

主要解决:一个对象更新,其它对象也要更新。

如何解决:目标类通过函数通知所有观察者自动更新。

关键代码:在目标类中增加一个ArrayList来存放观察者们。

例子:举一个追星例子,粉丝们特别关注明星A,当明星A发微博后,粉丝们手机能接受到该微博推送信息;当然粉丝们也可以取消对明星A的关注,以后手机就收不到消息推送了。这就可以理解为一个典型的发布-订阅的过程。

#include "ConcreteSubject.h"  // 迪丽热巴
#include "ConcreteObserver.h" // 粉丝

int main()
{
  //迪丽热巴 (发布者)
  Subject *subStarA = new ConcreteSubject();

  //粉丝 (观察者)
  Observer *oFan1 = new ConcreteObserver("observer1");
  Observer *oFan2 = new ConcreteObserver("observer2");

  subStarA->attach(oFan1); //oFan1微博关注迪丽热巴
  subStarA->attach(oFan2); //oFan2微博关注迪丽热巴

  subStarA->notify("大家好!我是迪丽热巴!"); //发微博通知所有粉丝

  return 0;
}

Subject(目标)
——目标知道它的观察者。可以有任意多个观察者观察同一个目标;
——提供注册和删除观察者对象的接口。

Observer(观察者)
——为那些在目标发生改变时需获得通知的对象定义一个更新接口。

ConcreteSubject(具体目标)
——将有关状态存入各ConcreteObserver对象;
——当它的状态发生改变时,向它的各个观察者发出通知。

ConcreteObserver(具体观察者)
——维护一个指向ConcreteSubject对象的引用;
——存储有关状态,这些状态应与目标的状态保持一致;
——实现Observer的更新接口以使自身状态与目标的状态保持一致。 

       

        当然我们这里不往深入的探究,只认为是两层的关系,实际上微博充当一个更新管理器就成三层的关系了,即 明星-微博-粉丝,明星发微博不用管信息是怎么传出去的。面对目标和观察者的依赖关系特别复杂时,可能需要一个维护这些关系的对象。当然一个粉丝也可以同时关注多个明星,这就是多对多的关系,此时更新管理器的存在就很有意义了。

        下面是一个使用到了观察者模式的MVC例子,数据模型为目标类,视图为观察者类。当数据模型发生改变时,通知视图类更新:

class View;

class DataModel   //目标抽象类   数据模型
{
public:
    virtual ~DataModel(){}
    virtual void add(View* view) = 0;
    virtual void remove(View* view) = 0;
    virtual void notify() = 0;   //通知函数
};

class View      //观察者抽象类   视图
{
public:
    virtual ~View() {cout << "~View()" << endl;}
    virtual void update() = 0;
};

//=============================================================//

class IntModel: public DataModel   //具体的目标类, 整数模型
{
public:
    ~IntModel()
    {
        clear();
    }
    void add(View* view)
    {
        auto iter = std::find(m_list.begin(), m_list.end(), view); //判断是否重复添加
        if(iter == m_list.end())
        {
            m_list.push_back(view);
        }
    }
    void remove(View* view)
    {
        auto iter = m_list.begin();
        for(;iter != m_list.end(); iter++)
        {
            if(*iter == view)
            {
                delete *iter;        //释放内存
                m_list.erase(iter);  //删除元素
                break;
            }
        }
    }
    void notify()  //通知观察者更新
    {
        auto iter = m_list.begin();
        for(; iter != m_list.end(); iter++)
        {
            (*iter)->update();
        }
    }
private:
    void clear()
    {
        if(!m_list.empty())
        {
            auto iter = m_list.begin();
            for(;iter != m_list.end(); iter++)  //释放内存
            {
                delete *iter;
            }
        }
    }
private:
    list<View*> m_list;
};


class TreeView: public View  //具体的观察者类   视图
{
public:
    TreeView(string name): m_name(name),View(){}
    ~TreeView(){ cout << "~TreeView()" << endl; }
    
    void update()
    {
        cout << m_name.data() << " : Update" << endl;
    }
private:
    string m_name;
}

=============== (未完待续) ===============


总结

        真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累。

        本文参考《大话设计模式》和《设计模式:可复用面向对象软件的基础》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值