`
soboer
  • 浏览: 1314390 次
文章分类
社区版块
存档分类
最新评论

【GOF设计模式之路】-- Singleton

 
阅读更多

之前一直徘徊第一篇该写哪一种设计模式,最后决定还是以Singleton模式开始吧。之所以以它开始,原因在我于个人认为,相对来说它在设计上比较单一,比较简单一些。在通常情况下,它是最容易理解的。同样也正因为它容易理解,细节才更值得注意,越是简单的东西,往往会被我们忽略一些细节。关于Singleton模式的讨论和实现也非常的多,GOF设计模式也只对它进行了简单的描述,本文则打算相对全面的介绍一下Singleton模式,目的在于挖掘设计的思想。

Singleton模式又称单件模式,我们都知道全局变量,Singleton就相当于一个全局变量,只不过它是一种被改进的全局变量。体现在哪儿呢?普通的全局变量可以有多个,例如某个类,可以定义很多个这个类的全局对象,可以分别服务于整个应用程序的各个模块。而Singleton则只允许创建一个对象,就好比不允许有克隆人一样,哪天在大街上看见一个一模一样的你,你会是什么感受?从计算机的角度,例如键盘、显示器、系统时钟等,都应该是Singleton,如果有多个这样的对象存在,很可能带来危险,而这些危险却不能换来切实的好处。

在GOF设计模式里,对Singleton的描述很简单:“保证一个类只有一个实体(instance),并为其提供一个全局访问点(global access point)”。所谓全局访问点,就是Singleton类的一个公共全局的访问接口,用于获得Singleton实体,对于用户来说,只需要简单的步骤就能获得这个实体,与全局变量的访问一样简单,而Singleton对象的创建与销毁有Singleton类自己承担,用户不必操心。既然是自己管理,我们就得将其管理好,让用户放心,现在是提倡服务质量的社会,我们应该有服务的态度。

理论讲了一大堆,迫不及待想看看具体实现,先看一个初期的版本:

/* Singleton.h */

//-----------------------------------------------------------------------------------
// Desc: Singleton Header File
// Author: masefee
// Date: 2010.09
// Copyright (C) 2010 masefee
//-----------------------------------------------------------------------------------

#ifndef __SINGLETON_H__
#define __SINGLETON_H__

class Singleton
{
public:
static Singleton* Instance( void );

public:
int doSomething( void );

protected:
Singleton( void ){}

private:
static Singleton* ms_pInstance;
};

#endif

/* Singleton.cpp */

//-----------------------------------------------------------------------------------
// Desc: Singleton Source File
// Author: masefee
// Date: 2010.09
// Copyright (C) 2010 masefee
//-----------------------------------------------------------------------------------

#include "Singleton.h"

Singleton* Singleton::ms_pInstance = 0;

Singleton* Singleton::Instance( void )
{
if ( ms_pInstance == 0 )
ms_pInstance = new Singleton;

return ms_pInstance;
}

int Singleton::doSomething( void )
{

}

/*main.cpp */

#include "Singleton.h"

int main( void )
{
Singleton* sig = Singleton::Instance();
sig->doSomething();
delete sig;

return 0;
}

Singleton类的构造函数被声明为protected,当然也可以声明为private,声明为protected是为了能够被继承。但用户不能自己产生Singleton对象,唯一能产生Singleton对象的就只有Instance成员函数,这个Instance函数即所谓的全局访问点。访问方式如上面main函数中的红色代码。如果用户没有调用Instance函数,Singleton对象就不会产生出来,这样优化的成本是Instance函数里的if检测,但是好处是,如果Singleton对象产生很昂贵,而本身有很少使用,这种“使用才诞生”的方案就会显尽优势了。

上面将ms_pInstance在全局初始化为0,这样做有个好处,即编译器在编译时就已经将ms_pInstance的初始值写到了可执行二进制文件里了,Singleton对象的唯一性在这个时期就已经决定了,这也正是C++实现Singleton模式的精髓所在。如果将ms_pInstance改造一下,如:

class Singleton
{
public:
static Singleton* Instance( void );

public:
void doSomething( void );

protected:
Singleton( void ){}

private:
static Singleton ms_Instance;
};

Singleton Singleton::ms_Instance;

Singleton* Singleton::Instance( void )
{
return &ms_Instance;
}

将ms_pInstance由指针改成了对象,这样做未必是件好事,原因在于ms_Instance是被动态初始化(在程序运行期间调用构造函数进行初始化)的,而ms_pInstance在前面已经说过,它是属于静态初始化,编译器在编译时就将常量写入到二进制文件里,在程序装载到内存时,就会被初始化。我们都知道,在进入main之前,有很多初始化操作,对于不同编译单元的动态初始化对象,C++并没有规定其初始化顺序,因此上面的改造方法存在如下隐患:

#include "Singleton.h"

int g_iRetVal = Singleton::Instance()->doSomething();

由于无法确保编译器一定先将ms_Instance对象初始化,所以全局变量g_iRetVal在被初始赋值时,Singleton::Instance()调用可能返回一个尚未构造的对象,这也就意味着你无法保证任何外部的对象使用的ms_Instance对象都是一个已经被正确初始化的对象。危险也就不言而喻了。

Singleton模式意为单件模式,于是保证其唯一性就成了关键,看看上面的第一种实现,ms_pInstance虽然是Instance成员函数所创建,它返回了一个Singleton对象的指针给外界,并且将这个指针的销毁权利赋予了外界,这就存在了第一个隐患,倘若外界将返回的指针给销毁了,然后再重新调用Instance函数,则前后对象内存地址通常将发生变化,如:

Singleton* sig1 = Singleton::Instance();
sig->doSomething();
delete sig;

Singleton* sig2 = Singleton::Instance();

如上,sig1和sig2的指向的Singleton对象的内存地址通常是不一样的,例如前面的sig1指向的对象保存了一些状态,这样销毁之后再次创建,状态已经被清除,程序也就容易出错了。所以为了避免这样类似的情况发生,我们再改进一下Instance函数:

static Singleton& Instance( void);

传回引用则不用担心被用户释放掉对象了,这样就比较安全了,Singleton对象都由其自身管理。

在C++类中,还有一个copy(复制)构造函数,在上面的Singleton类里,我们并没有显示声明copy构造,于是如果有以下写法:

Singleton sig( Singleton::Instance());

如果我们不显示声明一个copy构造,编译器会帮你生成一个默认的public版本的copy构造。上面的写法就会调用默认的copy构造,从而用户就能在外部声明一个Singleton对象了,这样就存在了第二个隐患。因此,我们将copy构造也声明为protected保护成员。

另外还有一个成员函数,赋值(assignment)操作符。因为你不能将一个Singleton对象赋值给另外一个Singleton对象,这违背了唯一性,不允许存在两个Singleton对象。因此我们将赋值操作符也声明为保护成员,同时对于Singleton来说,它的赋值没有意义,唯一性的原则就使它只能赋值给自己,所以赋值操作符我们不用去具体实现。

最后一个是析构函数,如前面所说,用户会在外界释放掉Singleton对象,为了避免这一点,所以我们也将析构函数声明为保护成员,就不会意外被释放了。

上述所有手段统一到一起之后,Singleton类的接口声明如下:

class Singleton
{
public:
static Singleton& Instance( void );

public:
void doSomething( void );

protected:
Singleton( void );
Singleton( const Singleton& other );
~Singleton( void );
Singleton& operator =( const Singleton& other );

private:
static Singleton* ms_pInstance;
};

这样似乎已经完美了,Singleton对象的创建完全有Singleton类自身负责了,再看前面的创建过程,ms_pInstance是一个指针,Singleton对象是动态分配(new)出来的,那么释放过程就得我们手工调用delete,否则将发生内存泄露。然而析构函数又被我们定义为保护成员了,因此析构问题还没有得到解决。

这成了一个比较棘手的问题,既要保证程序运行时整个范围的唯一性,又要保证在销毁Singleton对象时没有人在使用它,所以销毁的时机显得尤为重要,也比较难把握。

于是有人想到了一个比较简单的方案,不动态分配Singleton对象便可以自动销毁了,但销毁的最好时期是在程序结束时最好,于是想到了如下方案:

Singleton& Singleton::Instance( void )
{
static Singleton _inst;
return _inst;
}

_inst是一个静态的局部变量,它的初始化是在第一次进入Instance函数时,这属于执行期初始化,而与编译期间常量初始化不同,_inst对象初始化要调用构造函数,这不可能在编译期间完成,与:

int func( void )
{
static int a = 100; // 编译期间常量初始化
return a;
}

不同。a的值在编译期间就已经决定了,在应用程序装载到内存时就已经为100了,而非在第一次执行func函数时才被赋值为100。

_inst对象的销毁工作由编译器承担,编译器将_inst对象的销毁过程注册到atexit,它是一个标准C语言库函数,让你注册一些函数得以在程序结束时调用,调用次序与栈操作类似,后进先出的原则。atexit的原型:

int __cdecl atexit( void ( __cdecl* pFunc )( void ) );

在Instance函数的反汇编代码上有所体现(VS2008 Release 禁用优化(/Od)):

Singleton& Singleton::Instance( void )
{
00CB1040 push ebp
00CB1041 mov ebp,esp
static Singleton _inst;
00CB1043 mov eax,dword ptr [$S1 (0CB3374h)]
00CB1048 and eax,1
00CB104B jne Singleton::Instance+33h (0CB1073h)
00CB104D mov ecx,dword ptr [$S1 (0CB3374h)]
00CB1053 or ecx,1
00CB1056 mov dword ptr [$S1 (0CB3374h)],ecx
00CB105C mov ecx,offset _inst (0CB3370h)
00CB1061 call Singleton::Singleton (0CB1020h)
00CB1066 push offset `Singleton::Instance'::`2'::`dynamic atexit destructor for '_inst'' (0CB1880h)
00CB106B call atexit (0CB112Eh)
00CB1070 add esp,4
return _inst;
00CB1073 mov eax,offset _inst (0CB3370h)
}
00CB1078 pop ebp
00CB1079 ret

红色的一句汇编代码即是得到_inst的析构过程地址压入到atexit的参数列表,红色粗体则调用了atexit函数注册这个析构过程。这里所谓的析构过程并不是Singleton类的析构函数,而是如下过程:

`Singleton::Instance'::`2'::`dynamic atexit destructor for '_inst'':
00CB1880 push ebp
00CB1881 mov ebp,esp
00CB1883 mov ecx,offset _inst (0CB3370h)
00CB1888 call Singleton::~Singleton (0CB1030h)
00CB188D pop ebp
00CB188E ret

这也是一个函数,在此函数里再调用Singleton的析构函数,如蓝色那句汇编代码。道理很简单,由于Singleton的析构函数是__thiscall,需要传递类对象,所以不是直接call析构函数的地址。

这种方式销毁在大多数情况下是有效的,在实际中,这种方式也用得比较多。可以根据实际的情况,选择不同的机制,Singleton没有定死只能用哪种方式。

既然上述方式在大多数情况下是有效的,那么肯定就有一些情况会有问题,这就引出了KDLkeyboard、display、log问题,假设我们程序中有三个singletons:keyboard、display、log,keyboard和display表示真实的物体,log表示日志记录,可以是输出到屏幕或者记录到文件。而且log由于创建过程有一定的开销,因此在有错误时才会被创建,如果程序一直没有错误,则log将不会被创建。

假如程序开始执行,keyboard顺利创建成功,而display创建过程中出现错误,这是需要产生一条log记录,log也就被创建了。这时由于display创建失败了,程序需要退出,由于atexit是后注册的先调用,log最后创建,则也是最后注册atexit的,因此log最先销毁,这没有问题。但是log销毁了,如果随后的keyboard如果销毁失败需要产生一条log记录,而这是log已经销毁了,log::Instance会不明事理的返回一个引用,指向了一个log对象的空壳,此后程序便不能确定其行为了,很可能发生其他的错误,这也称之为"dead - reference"问题。

从上面的分析来看,我们是想要log最后销毁,不管它是在什么时候创建的,都得在keyboard和display之后销毁,这样才能记录它们的析构过程中发生的错误。于是我们又想到,可以通过记录一个状态,来作为"dead - reference"检测。例如定义一个static bool ms_bDestroyed变量来标记Singleton是否已经被销毁。如果已经销毁则置为true,反之置为false。

/* Singleton.h */

class Singleton
{
public:
static Singleton& Instance( void );

public:
void doSomething( void );

protected:
Singleton( void ){};
Singleton( const Singleton& other );
~Singleton( void );
Singleton& operator =( const Singleton& other );

private:
static Singleton* ms_pInstance;
static bool ms_bDestroyed;
};

/* Singleton.cpp */

#include <iostream>
#include "Singleton.h"

Singleton* Singleton::ms_pInstance = 0;
bool Singleton::ms_bDestroyed = false;

Singleton& Singleton::Instance( void )
{
if ( !ms_pInstance )
{
if ( ms_bDestroyed )
throw std::runtime_error( "Dead Reference Detected" );
else
{
static Singleton _inst;
ms_pInstance = &_inst;
}
}
return *ms_pInstance;
}

Singleton::~Singleton( void )
{
ms_pInstance = 0;
ms_bDestroyed = true;
}

void Singleton::doSomething( void )
{

}

这种方案能够准确的检测"dead - reference",如果Singleton已经被销毁,ms_bDestroyed成员被置为true,再次获取Singleton对象时,则会抛出一个std::runtime_error异常,避免程序存在不确定行为。这种方案相对来说比较高效简洁了,也可适用于一定场合。

但这种方案在有的时候也不能让我们满意,虽然抛出了异常,但是KDL问题还是没有被最终解决,只是规避了不确定行为。于是我们又想到了一种方案,即是让log重生,一旦发现log被销毁了,而又需要记录log,则再次创建log。这就能保证log至始至终一直存在了,我们只需要在Singleton类里添加一个新的成员函数:

class Singleton
{
... ... other member ... ...

private:
static void destroySingleton( void );
};

实现则为:

void Singleton::destroySingleton( void )
{
ms_pInstance->~Singleton();
}

Instance成员就得在改造一下了:

Singleton& Singleton::Instance( void )
{
if ( !ms_pInstance )
{
static Singleton _inst;
ms_pInstance = &_inst;

if ( ms_bDestroyed )
{
new( ms_pInstance ) Singleton;
atexit( destroySingleton );
ms_bDestroyed = false;
}
}
return *ms_pInstance;
}

destroySingleton与前面汇编那段相似,相当于这个工作让我们自己来做了,而不是让编译器来做。destroySingleton手动调用Singleton的析构函数,而destroySingleton又被我们注册到atexit。当ms_pDestroyed为真时,则再次在ms_pInstance指向的内存出创建一个新的Singleton对象,这里使用的是placement new操作符,它并不会新开辟内存(参见:利用C++的operator new实现同一对象多次调用构造函数)。之后,注册destroySingleton为atexit,在程序结束时调用并析构Singleton对象。如此而来,便保证了Singleton对象的生命期跨越整个应用程序。log如果作为这样一个Singleton对象,那么无论log在什么时候被销毁,都能记录所有错误日志了。

似乎到此已经就非常完美了,但是还有一点不得不提,使用atexit具有一个未定义行为:

void func1( void )
{

}

void func2( void )
{
atexit( func1 );
}

void func3( void )
{

}

int main( void )
{
atexit( func2 );
atexit( func3 );
return 0;
}

CC++标准里并没有规定上面这种情况的执行次序,按前面的后注册先执行的说法,按理说func1被最后注册,则应该最先执行它,但是它的注册是由func2负责的,这就得先执行func2,才能注册func1。这样就产生了矛盾,所以以编译器的次序为准,在VS2008下,上面的例子中,最先执行func3,再执行func2,最后执行func1。看起来像是一个层级关系,main函数的注册顺序是一层,后来的又是一层,也可以认为在被注册为atexit的函数里再注册其它函数时,其它函数的执行次序在当前函数之后,如果注册了多个,则最后注册的在当前函数执行后立即执行。

上面这种机制是通过延长Singleton的声明周期,它破坏了其正常的生命周期。很可能带来不必要的迷惑,于是我们又想到了一种机制:是否能够控制Singleton的寿命呢,让log的寿命比keyboard和display的寿命长,便能够解决KDL问题了。控制寿命还可以针对不同的对象,不单单只是Singleton,它可以说是一种可以移植的概念。

我们实现一个生命期管理器,如下:

有了这个管理器,就能设置Singleton对象的寿命了,pTrackerArray是按生命周期的长度进行升序排列的,最前面的就是最先销毁的,这与前面的销毁规则是一致的。对于前面的KDL问题,我们就可以将keyboard和display的寿命设置为1,log的寿命设置为2,keyboard和display不存在先后问题,寿命相同也不影响。log寿命为2,大于keyboard和display就行,保证在最后销毁。这样一来,在单个线程下的KDL问题就完美解决了。

既然上面说了是在单线程里,言外之意就会存在多线程问题,例如:

Singleton& Singleton::Instance( void )
{
if ( !ms_pInstance )
{
ms_pInstance = new Singleton;
}
return *ms_pInstance;
}

假如有两个线程要访问这个Instance,第一个线程进入Instance函数,并检测if条件,由于是第一次进入,if条件成立,进入了if,执行到红色代码。此时,有可能被OS的调度器中断,而将控制权交给另外一个线程。

第二个线程同样来到if条件,发现ms_pInstance还是为NULL,因为第一个线程还没来得及构造它就已经被中断了。此时假设第二个线程完成了new的调用,成功的构造了Singleton,并顺利的返回。

很不幸,第一个线程此刻苏醒了,由于它被中断在红色那句代码,唤醒之后,继续执行,调用new再次构造了Singleton,这样一来,两个线程就构建两个Singleton,这就破坏了唯一性。

我们意识到,这是一个竞态条件问题,在共享的全局资源对竞态条件和多线程环境而言都是不可靠的。怎么避免上面的这种情况呢,有一种简单的做法是:

Singleton& Singleton::Instance( void )
{
_Lock holder( _mutex);
if ( !ms_pInstance )
{
ms_pInstance = new Singleton;
}
return *ms_pInstance;
}

_mutex是一个互斥体,_Lock类专门用于管理互斥体,在_Lock的构造函数中对_mutex加锁,在析构函数中解锁。这样保证同一在锁定之后操作不会被其它线程打断。holder是一个临时的_Lock对象,在Instance函数结束时会调用其析构,自动解锁。这也是著名的RAII机制。

似乎这样做确实能够解决竞态条件的问题,在一些场合也是可以的,但是在需要更高效率的环境下,这样做缺乏效率,比起简单的if ( !ms_pInstance)测试要昂贵很多。因为每次进入Instance函数都加锁解锁一次,即使需要加锁解锁的只有第一次进入时。所以我们想要有这样的解法:

Singleton& Singleton::Instance( void )
{
if ( !ms_pInstance )
{
_Lock holder( _mutex );
ms_pInstance = new Singleton;
}
return *ms_pInstance;
}

这样虽然解决了效率问题,但是竞态条件问题又回来了,打不到我们的要求,因为两个线程都进入了if,再锁定还是会产生两个Singleton对象。于是有一个比较巧妙的用法,即“双检测锁定”Double-Checked Locking模式。直接看效果吧:

Singleton& Singleton::Instance( void )
{
if ( !ms_pInstance )
{
_Lock holder( _mutex );
if ( !ms_pInstance )
ms_pInstance = new Singleton;
}
return *ms_pInstance;
}

非常美妙,这样就解决了效率问题,同时还解决了竞态条件问题。即使两个线程都进入了第一个if,但第二个if只会有一个线程进入,这样当某个线程构造了Singleton对象,其它线程因为中断在_Lock holder( _mutex )这一句。等到唤醒时,ms_pInstance已经被构造了,第二个if测试就会失败,便不会再次创建Singleton了。第一个if显得很粗糙快速,第二个if显得清晰缓慢,第一个if是为了第二次进入Instance函数提高效率不再加锁,第二个if是为了第一次进入Instance避免产生多个Singleton对象,各施其职,简单而看似多余的if测试改进,显得如此美妙。

本文从开头到现在,一次又一次感到完美,又一次一次发现不足。到此,又似乎感到了完美,但完美背后还真容易有阴霾。虽然上面的双检测锁定已经在理论上胜任了这一切,趋近于完美。但是,有经验的程序员,会发现它还是存在一个问题。

对于RISC(精简指令集)机器的编译器,有一种优化策略,这个策略会将编译器产生出来的汇编指令重新排列,使代码能够最佳运用RISC处理器的平行特性(可以同时执行几个动作)。这样做的好处是能够提高运行效率,甚至可以加倍。但是不好之处就是破坏了我们的“完美”设计“双检测锁定”。编译器很可能将第二个if测试的指令排列到_Lock holder( _mutex )的指令之前。这样竞态条件问题又出现了,哎!

碰到这样的问题,就只有翻翻编译器的说明文档了,另外可以在ms_pInstance前加上volatile修饰,因为合理的编译器会为volatile对象产生恰当而明确的代码。

到此,常见的问题都基本解决了,不管是多线程还是单线程,在具体的环境我们再斟酌选择哪一种方式,因此,本文并没有给出一个统一的解决方案。你还可以将上面的机制组装到一起,写成一个SingletonHolder模板类,在此就不实现了。Singleton还能根据具体进行扩展,方法也不止上面这些,我们只有一个目的,让它正确的为我们服务。

在本文开头说Singleton是一个相对好理解的一种设计模式,但从整篇下来,它也并不是那么单纯。由简单到复杂,每一种设计方案都有它的用武之地,例如,我们的程序里根本就不会出现KDL问题,那么就可以简单处理。再者我们有的Singleton不可能在多线程环境里运行,那么我们也没有必要设计多线程这一块,而只需要在考虑问题时意识到就可以了。做到一切尽在掌握之中即可。

好吧!本文就到此结束,重在体会这些细节的机制和挖掘问题然后解决问题的乐趣。在此感谢《Modern C++ Design》,望大家多提意见,感谢!!

【GOF设计模式之路】目录

【GOF设计模式之路】-- 开篇

【GOF设计模式之路】-- Singleton

【GOF设计模式之路】-- Factory

【GOF设计模式之路】-- Observer

分享到:
评论

相关推荐

    设计模式精解-GoF 23种设计模式解析

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 目 录 引 言 0.1 设计模式解析(总序) 0.2 设计模式解析后记 0.3 与作者联系 1 创建型模式 1.1 Factory模式 1.2 AbstactFactory模式 1.3 Singleton...

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码.pdf

    设计模式精解- GoF 23 种设计模式解析附 C++实现源码 目 录 0 引言 ...........................................................................................................................................

    设计模式精解-GoF 23种设计模式

    工厂模式、抽象工厂模式、Singleton模式等等23种设计模式……欢迎大家下载阅读!

    设计模式精解-GoF 23种设计模式解析附C++实现源码.pdf

    设计模式精解-GoF 23种设计模式解析附C++实现源码 目 录 0 引言.........................................................................................................................................

    GoF 23种设计模式的详解与应用

    详细介绍GoF设计模式以及应用... 创建模式:设计模式之Factory,设计模式之Prototype(原型),设计模式之Builder,设计模式之Singleton(单态). 结构模式:设计模式之Facade(外观),设计模式之Proxy(代理),设计模式之...

    设计模式精解(GoF 23种设计解析附C++实现源码)

    0.1设计模式解析(总序).....................................................................................................2 0.2设计模式解析后记..........................................................

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码.rar

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 目 录 0 引言 ............................................................................................................................................

    设计模式 GOF 23

    本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用...

    GoF 的 23 种设计模式

    GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍。 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。 原型(Prototype)模式:将一...

    设计模式精解-GoF 23 种设计模式解析

    0.1 设计模式解析(总序)...............2 0.2 设计模式解析后记.......................2 0.3 与作者联系..................................5 1 创建型模式.............................................5 ...

    uu-design-pattern:23种设计模式案例

    23种设计模式演示代码文件结构图gof23 |- creational(创建型模式) |- simplefactory 简单工厂模式案例 |- factorymethod 工厂方法模式案例 |- abstractfactory 抽象工厂模式案例 |- builder 建造者模式案例 |- ...

    研磨设计模式(完整带书签).part2.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    gof:GoF的23种设计模式

    GoF 设计模式GoF所提出的23种设计模式主要基于以下面向对象设计原则:对接口编程而不是对实现编程优先使用对象组合而不是继承23种设计模式分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural ...

    研磨设计模式-part2

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-part4

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-part3

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    Scala和设计模式.pdf

    在当前软件Software设计中最流行要算GoF这本书中提出各种设计模式很多人认为设计模式对于语言(特 别是c/Java)本身不足的处或多或少有些弥补不过如果语言足够强大模式也许没有必要 下面Peter Norvig个例子就非常有...

    100-words-design-patterns-java:GoF设计模式,每个模式都描述了真实生活中的故事

    Java中的100个单词GoF设计模式 介绍 想法:以一种简单的方式描述GoF设计模式。每个模式将通过以下结构进行描述: 故事(少于100个字) 用Java实现 GoF设计模式 创作模式 结构模式 行为模式 辛格尔顿 动机 对象驻留...

    设计模式精解(GoF 23种设计模式解析附C实现源码) pdf

    0.1 设计模式解析(总序)........................... 0.2 设计模式解析后记................................... 0.3 与作者联系.............................................. 1 创建型模式......................

Global site tag (gtag.js) - Google Analytics