C++

Boolan(博览网)——C++ 设计模式(第十三周大结局!撒花

2018-01-10  本文已影响0人  Michael_SR

目录

1. 单件模式(Singleton)

2. 享元模式(Flyweight)

3. 状态模式(State)

4. 备忘录(Memento)

5. 组合模式(Composite)

6. 迭代器(Iterator)

7. 职责链(Chain of Responsibility)

8. 命令模式(Command)

9. 访问器(Visitor)

10. 解析器(Interpreter)

11. 设计模式总结


「对象性能」模式

1. 单件模式(Singleton)

动机

保证一个类仅有一个实例,并提供一个该实例的全局访问点。
——《设计模式》GoF

结构

笔记


class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

/*
在单线程环境下,以上的代码没问题,但是在多线程的情况下会出问题。
当线程1执行到 if (m_instance == nullptr) 时,如果这时候正好线程2获得了CPU的执行权,
那么,此时对于两个线程来说,都检测到了这个对象为空,
那么两者都会创建该对象,也就是会破坏了单例的本质
*/

/*
为了解决以上多线程的问题,就出现了下面的线程安全的版本,通过锁对象的方案来解决。
也就是说在一个线程执行到getInstance方法时,在锁对象未被释放前,不会交出CPU的执行权。
那么此时可以解决好多线程问题,但是另外一个问题同时产生,
那就是这样的代码,效率相对比较低,破坏了多线程机制。
如果在代码部署在服务器端,在对象创建的开始时,如果有两个客户端访问,
那么一个进入了锁对象,那么他必然会获得锁对象,
而另一个只有等待第一个用户完成后才能进入getIntances方法来获取对象。
并且对于对象创建完成之后,所有的getInstance方法来说,
都是读取这个进程,
但每次都会有一个锁对象。那么资源是浪费的。如果高并发的情况,也会拖累效率。
 */

//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

/*
那么,为了解决以上的问题,如果为空的情况,
也就是创建的时候才去创建锁对象 
通过这样的方法可以避免在读取的时候每次都创建锁对象。
但是在这个代码中,必须要对所创建的对象判空两次。
因为如果只判一次空,还是会出现线程安全的问题。
 */

/*
对于双检查看起来已经很好的完成了Singleton的要求和线程安全的问题。但实际上很容易出问题。

但是以上的代码实际存在漏洞,双检查在内存读写时会出现reorder不安全的情况。

reorder:我们看代码有一个指令序列,但代码在汇编之后,可能在执行的时候,抢CPU的指向权的时候,可能和我们预想的不一样。

一般m_instance = new Singleton();只想的时候我们认为是先分配内存,再调用构造函数创建对象,再把对象的地址赋值给变量。
但在CPU实际执行的时候,以上的三个步骤可能会被重新打乱顺序执行。
可能会是先分配内存,然后就把内存地址直接赋值给变量,最后在调用构造函数来创建对象。
那么如果出现以上的reorder的情况,变量已经被赋值了对象的指针,但实际却指向了没被初始化的内存。
那么此时,线程安全问题就再次出现了。
 */

//双检查锁,但由于内存读写reorder不安全(已经被弃用)
Singleton* Singleton::getInstance() {

    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();//我们以为会先分配内存,再调用构造器,最后把地址赋值给m_instance
                                         //但因为 reorder,很可能真实顺序是先分配内存,地址赋值,再调用构造器
        }
    }
    return m_instance; //另一个进程可能看到 m_instance 非空,就返回了,但可能构造器还未调用,就返回了一个原生地址
}

/*
 在java和C#这类语言来说,增加了一个volatile关键字,通过他来修饰单例的对象,此时编译器不会在进行reorder的优化编译,以此保证代理的正确性。

2005年VC的编译器自己添加了volatile关键字,但跨平台的问题没办法解决。直到C++11后才真正的解决了这个问题,实现了跨平台。
具体代码如下:
 */

//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

lock 是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

饿汉式单件类:静态初始化,在自己被加载时就将自己实例化。
懒汉式单件类:第一次被引用时,才会将自己实例化。

要点总结

2. 享元模式(Flyweight)

动机

运用共享技术有效地支持大量细粒度的对象。
——《设计模式》GoF

结构

笔记

要点总结


「状态变化」模式

3. 状态模式(State)

动机

允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。
——《设计模式》GoF

结构

笔记

要点总结

4. 备忘录(Memento)

动机

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
——《设计模式》GoF

结构

笔记

要点总结


「数据结构」模式

5. 组合模式(Composite)

动机

将对象组合成树形结构以表示“部分-整体”的层级结构。Compisite使得用户对单个对象和组合对象的使用具有一致性(稳定)。
——《设计模式》GoF

结构

笔记

要点总结

6. 迭代器(Iterator)

动机

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(隔离变化,稳定)该对象的内部表示。
——《设计模式》GoF

结构

笔记

要点总结

7. 职责链(Chain of Responsibility)

动机

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
——《设计模式》GoF

结构

笔记

要点总结


“行为变化”模式

8. 命令模式(Command)

动机

将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
——《设计模式》GoF

结构

=

笔记

要点总结

9. 访问器(Visitor)

动机

表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
——《设计模式》GoF

结构

笔记

要点总结


「领域规则」模式

10. 解析器(Interpreter)

动机

给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。
——《设计模式》GoF

结构

笔记

要点总结:


11. 设计模式总结

一个目标

管理变化,提高复用!

两种手段

分解 VS. 抽象

八大原则(烂熟于心!)

重构技法

从封装变化角度对模式分类

C++ 对象模型

关注变化点和稳定点

什么时候不用模式

经验之谈

设计模式成长之路


祝工作顺利,学习愉快,早日财务自由,踏入幸福之路,谢谢大家!

上一篇 下一篇

猜你喜欢

热点阅读