【一文快速理解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;
}

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


总结

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

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

目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象接口 9 1.6.4 描述对象的实现 10 1.6.5 运用复用机制 13 1.6.6 关联运行时刻和编译时刻的 结构 15 1.6.7 设计应支持变化 16 1.7 怎样选择设计模式 19 1.8 怎样使用设计模式 20 第2章 实例研究:设计一个文档编 辑器 22 2.1 设计问题 23 2.2 文档结构 23 2.2.1 递归组合 24 2.2.2 图元 25 2.2.3 组合模式 27 2.3 格式化 27 2.3.1 封装格式化算法 27 2.3.2 Compositor和Composition 27 2.3.3 策略模式 29 2.4 修饰用户界面 29 2.4.1 透明围栏 29 2.4.2 Monoglyph 30 2.4.3 Decorator 模式 32 2.5 支持多种视感标准 32 2.5.1 对象创建的抽象 32 2.5.2 工厂类和产品类 33 2.5.3 Abstract Factory模式 35 2.6 支持多种窗口系统 35 2.6.1 我们是否可以使用Abstract Factory 模式 35 2.6.2 封装实现依赖关系 35 2.6.3 Window和WindowImp 37 2.6.4 Bridge 模式 40 2.7 用户操作 40 2.7.1 封装一个请求 41 2.7.2 Command 类及其子类 41 2.7.3 撤消和重做 42 2.7.4 命令历史记录 42 2.7.5 Command 模式 44 2.8 拼写检查和断字处理 44 2.8.1 访问分散的信息 44 2.8.2 封装访问和遍历 45 2.8.3 Iterator类及其子类 46 2.8.4 Iterator模式 48 2.8.5 遍历和遍历过程中的动作 48 2.8.6 封装分析 48 2.8.7 Visitor 类及其子类 51 2.8.8 Visitor 模式 52 2.9 小结 53 第3章 创建型模式 54 3.1 Abstract Factory(抽象工厂)— 对象创建型模式 57 3.2 Builder(生成器)—对象创建型 模式 63 3.3 Factory Method(工厂方法)— 对象创建型模式 70 3.4 Prototype(原型)—对象创建型 模式 87 3.5 Singleton(单件)—对象创建型 模式 84 3.6 创建型模式的讨论 89 第4章 结构型模式 91 4.1 Adapter(适配器)—类对象结构型 模式 92 4.2 Bridge(桥接)—对象结构型 模式 100 4.3 Composite(组成)—对象结构型 模式 107 4.4 Decorator(装饰)—对象结构型 模式 115 4.5 FACADE(外观)—对象结构型 模式 121 4.6 Flyweight(享元)—对象结构型 模式 128 4.7 Proxy(代理)—对象结构型 模式 137 4.8 结构型模式的讨论 144 4.8.1 Adapter与Bridge 144 4.8.2 Composite、Decorator与Proxy 145 第5章 行为模式 147 5.1 CHAIN OF RESPONSIBIL ITY(职责链) —对象行为型模式 147 5.2 COMMAND(命令)—对象行为型 模式 154 5.3 INTERPRETER(解释器)—类行为型 模式 162 5.4 ITERATOR(迭代器)—对象行为型 模式 171 5.5 MEDIATOR(中介者)—对象行为型 模式 181 5.6 MEMENTO(备忘录)—对象行为型 模式 188 5.7 OBSERVER(观察者)—对象行为型 模式 194 5.8 STATE(状态)—对象行为型模式 201 5.9 STRATEGY(策略)—对象行为型 模式 208 5.10 TEMPLATE METHOD(模板方法) —类行为型模式 214 5.11 VISITOR(访问者)—对象行为型 模式 218 5.12 行为模式的讨论 228 5.12 1 封装变化 228 5.12.2 对象作为参数 228 5.12.3 通信应该被封装还是被分布 229 5.12.4 对发送者和接收者解耦 229 5.12.5 总结 231 第6章 结论 232 6.1 设计模式将带来什么 232 6.2 一套通用的设计词汇 232 6.3 书写文档和学习的辅助手段 232 6.4 现有方法的一种补充 233 6.5 重构的目标 233 6.6 本书简史 234 6.7 模式界 235 6.8 Alexander 的模式语言 235 6.9 软件中的模式 236 6.10 邀请参与 237 6.11 临别感想 237 附录A 词汇表 238 附录B 图示符号指南 241 附录C 基本类 244 参考文献 249
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值