设计模式——单例模式与工厂模式

1. 单例模式

确保某一个对象只有一个实例,而且自行实例化并向整个程序提供这个实例。

优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  • 避免对资源的多重占用。如避免对同一个资源文件的同时写操作。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问。

缺点

  • 单例模式一般没有接口,扩展困难。
  • 不利于测试。

使用场景

  • 要求生成唯一序列号的环境。
  • 在整个项目中需要一个共享访问点或共享数据。
  • 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源。
  • 需要定义大量的静态常量和静态方法的环境。

实现

懒汉实现方法,即实例化在对象首次被访问时进行。可以使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。同时需将默认构造函数声明为private,防止用户调用默认构造函数创建对象。

class CSingleton
{
private:
    CSingleton(){};
    static CSingleton* m_pInstance;
    public:
    static CSingleton* GetInstance(){
        if (m_Instance == NULL )
        {
            Lock(); 
            if (m_Instance == NULL )
            {
                m_Instance = new Singleton ();
            }
            UnLock(); 
        }
        return m_pInstance;
    }
};

此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。

上面的实现存在一个问题,就是没有提供删除对象的方法。一个妥善的方法是让这个类自己知道在合适的时候把自己删除,或者说把删除自己的操作挂在操作系统中的某个合适的点上,使其在恰当的时候被自动执行。

我们知道,程序在结束的时候,系统会自动析构所有的全局变量。事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):

class CSingleton
{
private:
    CSingleton(){};
    static CSingleton* m_pInstance;
    class CGarbo
    {
    public:
        ~CGarbo(){
            if(CSingleton::m_pInstance != NULL)
            delete CSingleton::m_pInstance;
        }
    };
    static CGarbo garbo;
public:
    static CSingleton* GetInstance(){
        if (m_Instance == NULL )
        {
            Lock(); 
            if (m_Instance == NULL )
            {
                m_Instance = new Singleton ();
            }
            UnLock(); 
        }
        return m_pInstance;
    }
};

类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。

饿汉实现方法:在程序开始时就自行创建实例。如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。

class CSingleton
{
private:
    CSingleton(){};
    static CSingleton* m_pInstance;
    class CGarbo
    {
    public:
        ~CGarbo(){
            if(CSingleton::m_pInstance != NULL)
            delete CSingleton::m_pInstance;
        }
    };
    static CGarbo garbo;
public:
    static CSingleton* GetInstance(){
        return m_pInstance;
    }
};

CSingleton *CSingleton::m_pInstance = new CSingleton();

"C++设计模式——单例模式"

2. 工厂方法模式

介绍

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个产品类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

应用实例:

  1. 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
  2. Hibernate 换数据库只需换方言和驱动就可以。

优点:

  1. 一个调用者想创建一个对象,只要知道其名称就可以了。
  2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  3. 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

**使用场景: **

  1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  3. 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

实现

简单工厂模式

#include <iostream>
using namespace std;

class Shape
{
public:
    virtual void draw()=0;
};

class Square : public Shape
{
public:
    void draw(){
        cout<<"This is a square."<<endl;
    }
};

class Rectangle : public Shape
{
public:
    void draw(){
        cout<<"This is a rectangle."<<endl;
    }
};

class Circle :public Shape
{
public:
    void draw(){
        cout<<"This is a circle"<<endl;
    }
};

class SimpleFactory
{
public:
    typedef enum ShapeType
    {
        ShapeSquare,
        ShapeRectangle,
        ShapeCircle
    }SHAPETYPE;
    Shape* CreateShape(SHAPETYPE type)
    {
        switch(type)
        {
        case ShapeSquare:
            return new Square();
        case ShapeRectangle:
            return new Rectangle();
        case ShapeCircle:
            return new Circle();
        default:
            return NULL;
        }
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    SimpleFactory* myFactory = new SimpleFactory();
    Shape* myShape = myFactory->CreateShape(SimpleFactory::ShapeCircle);
    myShape->draw();
    delete myShape;
    myShape = NULL;
    delete myFactory;
}

上述实现需要手动释放申请在堆上的内存,可以利用智能指针实现自动释放。

工厂方法模式
拥有几个工厂,都派生自同一抽象工厂类,可以生产不同的产品。
C++设计模式——工厂方法模式

class AbstractProduct
{
public:
    virtual void operation()=0;
};

class ProductA : public AbstractProduct
{
public:
    virtual void operation(){
        cout<<"This is A!"<<endl;
    }
};

class ProductB : public AbstractProduct
{
public:
    virtual void operation(){
        cout<<"This is B!"<<endl;
    }
};

class AbstractFactory
{
public:
    virtual AbstractProduct* createProduct()=0;
};

class FactoryA : public AbstractFactory
{
public:
    AbstractProduct* createProduct(){
        return new ProductA();
    }
};

class FactoryB : public AbstractFactory
{
public:
    AbstractProduct* createProduct(){
        return new ProductB();
    }
};

int main()
{
    AbstractFactory* myFactory = new FactoryA();
    AbstractProduct* myProduct = myFactory->createProduct();
    myProduct->operation();
    delete myProduct;
    myProduct = NULL;
    delete myFactory;
    myFactory = NULL;
}

注意:在上述两个实现中,为简单起见,并没有定义构造函数和虚构函数。其中,基类的虚构函数应为虚函数,这样才能保证,delete操作能够调用合适的析构函数。

抽象工厂模式
拥有几个抽象工厂类。

3. 建造者模式

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

介绍

主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

应用实例:

  1. 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
  2. JAVA 中的 StringBuilder。

**优点: **

  1. 建造者独立,易扩展。
  2. 便于控制细节风险。

**缺点: **

  1. 产品必须有共同点,范围有限制。
  2. 如内部变化复杂,会有很多的建造类。

**使用场景: **

  1. 需要生成的对象具有复杂的内部结构。
  2. 需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • 前言 设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中...
    IT人生阅读 3,900评论 0 5
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,922评论 1 15
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,228评论 4 34
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,628评论 18 139
  • 1 单例模式的动机 对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子——Windows任务...
    justCode_阅读 1,432评论 2 9