设计模式之单例

  设计模式系列博客是学习《大话设计模式》的学习笔记,期间参考了一些网络上的资源,设计模式之路,才刚刚开始,现在掌握理论知识,写一些简单的demo,今后希望能灵活的应用在项目中,让自己的代码能够具有更高的可复用性和可扩展性,示例代码中,方法、变量、类的命名可能并不符合规范,今后要多阅读优秀的代码,在编程中遵循《Goocle C++ 编程指南》,养成良好的代码风格,为自己也为团队留下优质的代码。本系列博客中所有的demo都是在CentOS7.4 - 64 位下写的,gcc版本为4.8.5。有学习在linux下使用gcc编译器写C++代码的可以一起交流一下,分享下学习资料和学习心得乃至经验,自己一个人捣鼓真的太痛苦了。中间有什么写的不好的地方,请多多指教。谢谢。

  编程是一门技术,是我们赖以生活的职业技能,但是除此之外呢?《大话设计模式》这本书里说编程是一门艺术。我相信它确实是一门艺术,计算的艺术,也是人类文明进化史上最具有奇思妙想的一门艺术之一。最近我在想,编程对我来说意味着什么?是数之不尽的需求变更,还是和这个不完善的体系或者没有什么解决问题思维的人又或者对系统没有宏观认知的人斗智斗勇还是不理世事,任劳任怨埋头苦干,赚一口饭钱呢?我觉得都不是,有人说只有生活美满的人才能写出完美的代码,那么如果觉得心力交瘁可能是一种不太正常的状态。面对编程,说不上追求技术的极致那么夸张(技术永无边界),但至少要追求更高、更快、更强吧。编程到底意味着什么呢?有些人为钱而来,有些人不知何所来。总之无论如何,都不要磨灭对技术的热情,永远保持求知若渴的心态。

  最后感谢《大话设计模式》这本书,提供了如此之好的启蒙方式,感谢网络上的各路大神提供的优质资源。

知识补给站

  CentOS7.4支持中文显示

  写这个系列博客的时候还不知道这回事,所以输出部分都是拿英文写的,英文水平,实在是令我汗颜。上面引用的另外一篇博客是讲在系统(不带图形界面)已经安装好的情况下怎么支持中文显示的操作,经过我实际操作确实是有效的。

  

  我打算在这部分链接一些其它的基本知识,例如继承的知识。但是现在还没有写好,写好之后会把链接放在这里,还有一些我在学习怎么在linux下用C++进行编程的一些心得也会放在这里,以备日后查阅。最后学号英语真的很重要。

设计模式分类

  最早提出设计模式的时候总共有23种,可以分为3大类:(Gof)

设计模式分类
类型 描述
创建型模式(Creational Patterns) 用于构建对象,以便它们可以从实现系统中分离出来。
结构型模式(Structural Patterns) 用于在许多不同的对象之间形成大型对象结构
行为型模式(Behavioral Patterns) 用于管理对象之间的算法、关系和职责

创建型模式:

  单例模式(Singleton Pattern) 
  保证一个类仅有一个实例,并提供一个访问它的全局访问点。

        简单工厂模式

  简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。

       工厂模式(Factory Method Pattern)

  定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。工厂模式是简单工厂模式的升级版。

    抽象工厂模式(Abstract Factory Pattern)

       提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类,抽象工厂模式是工厂模式的升级版。

  建造者模式(Builder Pattern)

  将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  原型模式(Prototype Pattern)

  用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

  

 

结构型模式:

  装饰模式(Decorator)

  动态的给一个对象添加一些额外的职责,就添加功能来说,装饰模式比生成子类更为灵活。

        代理模式(Proxy Pattern)

  代理(Proxy)模式,为其它对象提供一种代理以控制对这个对象的访问。在某些情况下一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

  享元模式(Flyweight Pattern)

  享元模式(Flyweight),运用共享技术有效的支持大量细粒度的对象。

  组合模式(Composite Pattern)

  组合模式(Composite),将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象(即叶子构件)和组合对象(即组合构件)的使用具有一致性。(例如,你可以在word里对单个字和一行字采用同样的操作)注意,这里说的树就是一颗树,没有任何的限制,它可以是任何形状的。这棵树它是靠对象之间的组合关系构建起来,而非数据结构意义上的树。

  桥接模式(Bridge Pattern)

  桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。这里的抽象与它的实现分离,并不是指让抽象类和派生类分类,这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。其实就是说,一个系统的实现,可能有多个角度的分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独自变化。举例来说,手机即可以按照品牌分类(手机是抽象,功能是实现),也可以按照功能分类(功能是实现,而手机变成了抽象)。

  外观模式(Facade Pattern)

  外观模式(Facade Pattern)又称门面模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

  适配器模式(Adapter Pattern)

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

行为型模式

  策略模式(Strategy Pattern)

  策略(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

  模板模式(Template Pattern)

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

  观察者模式(Observer Pattern)

  观察者模式又叫做发布-订阅(Publish/Subscribe)模式。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。

  状态(State Pattern)模式

  状态(State)模式,当一个对象的内在状态改变时允许改变其行为,这个对象看起来就像是改变了其类。状态模式主要解决的是当控制一个对象状态转换条件表示式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然若果这个判断条件很简单,那么就没有必要用状态模式了。

  备忘录(Memento Pattern)模式

  备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原来保存的状态。

  迭代器(Iterator Pattern)模式

  遍历:所谓遍历,就是指把一个集合中的所有元素挨个访问一遍(这里的访问,就是它的字面意思)。

  迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

  命令模式(Command Pattern)模式

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

  职责链(Chain Of Responsibility Pattern)模式

  职责链(Chain Of Responsibility)模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。

  这里发出这个请求的客户端并不知道这当中的哪一个对象最终处理这个请求,这样系统的更改可以在不影响客户端的情况下动态的重新组织和分配责任。

  中介者(Mediator Pattern)模式

  中介者(Mediator)模式,用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式的互相引用,从而使得其耦合松散,而且可以独立的改变他们之间的交互。

  解释器(Interpreter Pattern)模式

  解释器(Interpreter)模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

  访问者(Visitor Pattern)模式

  访问者(Visitor)模式,表示一个作用于某对象结构中各个元素的操作。它使你可以在不改变各元素的前提下定义作用于这些元素的新操作。

单例概述

       单例意即类在整个工程里只能有一个实例。单例通常应用在如下场景中,类的构造是一个非常耗时的过程,并且,它没有多次构造的必要性。例如。你可以打开一个数据库连接,只在此连接上进行数据库操作。

  那么怎确保它在整个工程中只有一个实例呢?我们可以通过将构造函数的访问权限设置为private,并辅助其它手段来保证,同时将拷贝构造函数和赋值构造函数声明为delete的。

知识储备

作用域

   在C++中,变量根据定义的位置不同具有不同的生命周期,具体分为六种:语句作用域、类作用域、全局作用域、文件作用域、命名空间作用域、局部作用域(函数或者语句块)。相应的变量也分为局部变量、全局变量、局部静态变量和全局静态变量。

局部变量、全局变量、局部静态变量、全局静态变量

  生存周期和作用域:生存周期指的是变量从定义开始到销毁经历的时间范围,而作用域指的是变量的可见代码域。

       局部变量:局部变量具有局部作用域,例如函数的形参,定义在函数中的变量。从存储空间上来看,局部变量是在栈上分配空间的。从生存周期来看,它仅存在与被定义时到离开局部作用域的那一刻。

       全局变量:全局变量具有全局作用域,意即,一个全局变量只能有一个定义,可以有多个声明,其它文件需要使用这个全局变量的话,需要使用extern进行声明,它被定义于任何函数(包括main函数)之外。从存储的角度来看,它被保存在了ELF的.data段或者.bss段(根据是否被初始化而定)。从生存周期来看,它存在于整个程序运行期间,直到程序退出。

        静态局部变量:静态局部变量就是在局部变量的前面加了static修饰符,它的作用域范围和局部变量相同,生存周期从定义时起,到进程结束时由操作系统负责销毁。从空间分配上来说它在ELF的.data段。

        静态全局变量:静态全局变量就是在全局变量的前面加了static修饰符,它具有文件作用域,所谓文件作用域即指这个变量仅在定义它的文件中生效,对其它文件不可见,就是说可以在文件A和文件B中定义两个同名的静态全局变量。从空间分配的角度看,它在ELF文件的data段。

        那么,类的数据成员怎么分类呢?实际上,类的数据成员不适用于上述分类方式。普通数据成员就是类的实例的一部分,实例在,在数据成员在,实例不在,则数据成员亡。对静态数据成员,它则是属于类本身的,假设我们有一个数据,需要多个对象共享,那么可以使用静态数据成员。

单例-饿汉模式

  所谓饿汉模式即指无论该单例在工程中是否使用,都创建好这个单例。在C++11下饿汉模式的构建利用了静态变量在main函数开始执行前即初始化的行为。具体实现如下:

 

#ifndef SINGLETON_H_
#define SINGLETON_H_
#include <string>
#include <iostream>
class Singleton
{
private:
    static Singleton *m_SingleInstance; 
    std::string m_strInfo;
    Singleton(const Singleton &) = delete;    //copy Construct
    Singleton& operator=(const Singleton &)=delete;    //assign Construct 
    Singleton()
    {
        std::cout << "I am Constructed!"<< std::endl;
    }
    ~Singleton() = default;
public:
    void setInfo(const std::string strInfo)
    {
    m_strInfo = strInfo;
    }
    void getInfo(std::string &strInfo)
    {
    strInfo = m_strInfo;
    }
    static Singleton* getInstance();
};
#define SINGLETON Singleton::getInstance()
#endif

 

#include "Singleton.h"

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

Singleton* Singleton::getInstance()
{
    return m_SingleInstance;
}
#include "Singleton.h"

using namespace std;

int main(int argc,char *argv[])
{
    return(1);
}

 

       还有一个小小的MakeFile,第一次写这个欢迎指正:

VPATH = ../
main:Singleton.o main.o
    g++ -g $^ -o main.out -std=c++11
main.o:main.cpp
    g++ -g -c $^ -o $@ -std=c++11
Singleton.o:Singleton.cpp
    g++ -g -c $^ -o $@ -std=c++11
.PHONY:clean
clean: 
    rm -r *.*

 

  可以看到这个main函数里没有任何对单例的引用,使用GDB调试,在main函数入口处打上断点,可以看到在main函数还没有进入时就打印了Singleton的构造函数内的输出信息,这意味着类的静态数据成员是在main函数进入之前就被初始化了的,所以使用这种方式创建的单例没有线程安全的隐患。

单例-----懒汉模式

  所谓懒汉模式,就是指我们只有在需要的时候才去实例化这个单例,这个叫做延后初始化,实现思路是采用局部静态变量。采用局部静态变量实现的静态变量是线程安全的,这是C++11的规定。

  代码示例:

#ifndef SINGLETONIDLER_H_
#define SINGLETONIDLER_H_
#include <iostream>
#include <string>

class SingletonIdler
{
public:
    std::string getInfo()
    {
    return m_strInfo;
    }
    void setInfo(const std::string &strInfo)
    {
    m_strInfo = strInfo;
    }
    static SingletonIdler& getInstance();
private:
    std::string m_strInfo;
    SingletonIdler() = default;
    ~SingletonIdler() = default;
    SingletonIdler(const SingletonIdler &) = delete;
    SingletonIdler & operator = (const SingletonIdler &) = delete;
};
#define SINGLETONIDLER SingletonIdler::getInstance()
#endif
SingletonIdler.h
#include "SingletonIdler.h"
SingletonIdler& SingletonIdler::getInstance()
{
    static SingletonIdler myInstance;
    return myInstance;
}
SingletonIdler.cpp
#include "SingletonIdler.h"

using namespace std;
int main(int argc,char *argv[])
{
    SINGLETONIDLER.setInfo("Hello World");
    cout << SINGLETONIDLER.getInfo() << endl;
    return (1);
}
main.cpp

 

posted on 2018-09-14 01:57  古I月  阅读(279)  评论(0编辑  收藏  举报

导航