锘??xml version="1.0" encoding="utf-8" standalone="yes"?> Signed by :
David Bacon (IBM Research)
Joshua Bloch (Javasoft),
Jeff Bogda,
Cliff Click (Hotspot JVM project),
Paul Haahr,
Doug Lea,
Tom May,
Jan-Willem Maessen,
Jeremy Manson,
John D. Mitchell (jGuru)
Kelvin Nilsen,
Bill Pugh,
Emin Gun Sirer
Double-Checked Locking is widely cited and used
as an efficient method for implementing
lazy initialization
in a multithreaded environment.
Unfortunately, it
will not work reliably in a platform independent way
when implemented in Java, without additional synchronization.
When implemented in other languages, such as C++, it depends on
the memory model of the processor, the reorderings performed by
the compiler and the interaction between the compiler and the synchronization
library. Since none of these are specified in a language such as C++,
little can be said about the situations in which it will work. Explicit
memory barriers can be used to make it work in C++, but these barriers are
not available in Java.
To first explain the desired behavior, consider the following code:
澶у閮界煡閬擄紝鍦ㄧ敤C++鏉ュ疄鐜癝ingleton妯″紡鐨勬椂鍊欓氬父鎶婃瀯閫犲嚱鏁板0鏄庝負縐佹湁鐨勬垨鑰呬繚鎶ょ殑銆傚悓鏃跺0鏄庝竴涓叕鏈夌殑闈欐佺殑浼瀯閫犲嚱鏁幫紝閫氳繃瀹冩潵璋冪敤鐪熸鐨勬瀯閫犲嚱鏁般傚湪瀹炵幇榪欎釜浼瀯閫犲嚱鏁扮殑鏃跺欓氬父鏈変袱縐嶆柟寮忥細
class Singleton;
static Singleton& Singleton:;fakeSingleton()
{
static Singleton s;
return s;
}
絎簩縐嶆柟寮?
class Singleton{
public:
static Singleton* fakeSingleton();
...
private:
Singleton();
static Singleton * _instance;
}
Singleton* Singleton::fakesinketon()
{
if( _instance==NULL)
_instance=new Singleton();
return _instance;
}
瀵逛簬榪欎袱縐嶆柟寮忔垜瑙夊緱絎竴縐嶆洿濂戒竴浜涳紝鐞嗙敱鏄紝濡傛灉鏈変袱涓互涓婄殑綰跨▼鍚屾椂璁塊棶浼瀯閫犲嚱鏁扮殑鏃跺欐湁鍙兘鍚屾椂榪涘叆if 鎺у埗鍧楋紝榪欐牱灝辨湁鍙兘浜х敓涓や釜瀹炰緥錛侊紒鍥犳蹇呴』閲囩敤鐗規畩鐨勪繚鎶ゆ満鍒舵潵鎺у埗鍚屾銆傝岀涓縐嶆柟寮忎笉瀛樺湪榪欐牱鐨勯棶棰樸?
璇烽珮鎵嬫寚鐐癸紒鎴戜笉鏄庣櫧鐨勬槸錛屼負浠涔堜功涓婄殑渚嬪瓙榪樿緝澶氱殑閲囩敤絎簩縐嶆柟娉曪紵鑾潪瀹冩湁鑷繁鐨勪紭鍔匡紵錛?
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}
If this code was used in a multithreaded context, many things could go wrong. Most obviously, two or more Helper objects could be allocated. (We'll bring up other problems later). The fix to this is simply to synchronize the getHelper() method:
// Correct multithreaded version |
The code above performs synchronization every time getHelper() is called. The double-checked locking idiom tries to avoid synchronization after the helper is allocated:
// Broken multithreaded version |
Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.
There are lots of reasons it doesn't work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your fixes will not work: there are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.
Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.
The most obvious reason it doesn't work it that the writes that initialize the Helper object and the write to the helper field can be done or perceived out of order. Thus, a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor.
If the compiler inlines the call to the constructor, then the writes that initialize the object and the write to the helper field can be freely reordered if the compiler can prove that the constructor cannot throw an exception or perform synchronization.
Even if the compiler does not reorder those writes, on a multiprocessor the processor or the memory system may reorder those writes, as perceived by a thread running on another processor.
Doug Lea has written a more detailed description of compiler-based reorderings.
Paul Jakubik found an example of a use of double-checked locking that did not work correctly. A slightly cleaned up version of that code is available here.
When run on a system using the Symantec JIT, it doesn't work. In particular, the Symantec JIT compiles
singletons[i].reference = new Singleton();
to the following (note that the Symantec JIT using a handle-based object allocation system).
0206106A mov eax,0F97E78h
0206106F call 01F6B210 ; allocate space for
; Singleton, return result in eax
02061074 mov dword ptr [ebp],eax ; EBP is &singletons[i].reference
; store the unconstructed object here.
02061077 mov ecx,dword ptr [eax] ; dereference the handle to
; get the raw pointer
02061079 mov dword ptr [ecx],100h ; Next 4 lines are
0206107F mov dword ptr [ecx+4],200h ; Singleton's inlined constructor
02061086 mov dword ptr [ecx+8],400h
0206108D mov dword ptr [ecx+0Ch],0F84030h
As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).
Given the explanation above, a number of people have suggested the following code:
// (Still) Broken multithreaded version |
This code puts construction of the Helper object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment to the field helper.
Unfortunately, that intuition is absolutely wrong. The rules for synchronization don't work that way. The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released. It is perfectly reasonable and legal for the compiler to move the assignment helper = h; inside the synchronized block, in which case we are back where we were previously. Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.
There is something you can do to force the writer to perform a full bidirectional memory barrier. This is gross, inefficient, and is almost guaranteed not to work once the Java Memory Model is revised. Do not use this. In the interests of science, Do not use it.
However , even with a full memory barrier being performed by the thread that initializes the helper object, it still doesn't work.
The problem is that on some systems, the thread which sees a non-null value for the helper field also needs to perform memory barriers.
Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (e.g., a memory barrier), reads can be performed out of stale locally cached copies, even if other processors used memory barriers to force their writes into global memory.
I've created a separate web page with a discussion of how this can actually happen on an Alpha processor.
For most applications, the cost of simply making the getHelper() method synchronized is not high. You should only consider this kind of detailed optimizations if you know that it is causing a substantial overhead for an application.
Very often, more high level cleverness, such as using the builtin mergesort rather than handling exchange sort (see the SPECJVM DB benchmark) will have much more impact.
If the singleton you are creating is static (i.e., there will only be one Helper created), as opposed to a property of another object (e.g., there will be one Helper for each Foo object, there is a simple and elegant solution.
Just define the singleton as a static field in a separate class. The semantics of Java guarantee that the field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field.
class HelperSingleton { |
Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values (e.g., int's or float's). Note that it does not work for long's or double's, since unsynchronized reads/writes of 64-bit primitives are not guaranteed to be atomic.
// Correct Double-Checked Locking for 32-bit primitives |
In fact, assuming that the computeHashCode function always returned the same result and had no side effects (i.e., idempotent), you could even get rid of all of the synchronization.
// Lazy initialization 32-bit primitives |
It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions. For example, if you are programming in C++, you can use the code from Doug Schmidt et al.'s book:
// C++ implementation with explicit memory barriers |
Alexander Terekhov (TEREKHOV@de.ibm.com) came up clever suggestion for implementing double checked locking using thread local storage. Each thread keeps a thread local flag to determine whether that thread has done the required synchronization.
class Foo { |
The performance of this technique depends quite a bit on which JDK implementation you have. In Sun's 1.2 implementation, ThreadLocal's were very slow. They are significantly faster in 1.3, and are expected to be faster still in 1.4. Doug Lea analyzed the performance of some techniques for implementing lazy initialization.
As of JDK5, there is a new Java Memory Model and Thread specification.
JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write. See this entry in Jeremy Manson's blog for more details.
With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.
// Works with acquire/release semantics for volatile |
If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.
鏈枃妗g粰鍑鴻璁℃ā寮忎箣鈥斺擜bstractFactory妯″紡鐨勭畝鍖栬癄閲婏紝騫剁粰鍑哄叾C++瀹炵幇銆?/p>
Project |
Design Pattern Explanation錛圔y K_Eckel錛?/p> |
Authorization |
Free Distributed but Ownership Reserved |
Date |
|
Test Bed |
MS Visual C++ 6.0 |
鍦ㄦ湰鏂囨。鐨勫啓浣滀腑錛屽弬鑰冧簡浠ヤ笅鐨勮祫婧愶紝鍦ㄦ鍒楀嚭琛ㄧず鎰熻阿錛?/p>
u 涔︾睄
[GoF 2000]錛欸oF,Design Patterns-Elements of Reusable Object-Oriented Software
Addison-Wesley 2000/9.
[Martine 2003]錛歊obert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.
Author |
K_Eckel |
State |
Candidate for Master’s Degree School of |
E_mail |
鍋囪鎴戜滑瑕佸紑鍙戜竴嬈炬父鎴忥紝褰撶劧涓轟簡鍚稿紩鏇村鐨勪漢鐜╋紝娓告垙闅懼害涓嶈兘澶ぇ錛堣澶у閮芥病鏈変俊蹇冧簡錛屼及璁℃父鎴忎篃灝辨病鏈夊墠閫斾簡錛夛紝浣嗘槸涔熶笉鑳藉お綆鍗曪紙娌℃湁鎸戞垬鎬т篃涓嶇鍚堢帺瀹剁殑蹇冪悊錛夈備簬鏄垜浠氨鍙互閲囩敤榪欐牱涓縐嶅鐞嗙瓥鐣ワ細涓烘父鎴忚绔嬬瓑綰э紝鍒濈駭銆佷腑綰с侀珮綰х敋鑷蟲湁BT綰с傚亣璁句篃鏄繃鍏崇殑娓告垙錛屾瘡涓叧鍗¢兘鏈変竴浜涙墿錛坢onster錛夊畧鐫錛岀帺瀹惰鎶婅繖浜涙? 鐗╁共鎺夋墠鍙互榪囧叧銆備綔涓哄紑鍙戣咃紝鎴戜滑灝變笉寰椾笉鍒涘緩鎬墿鐨勭被錛岀劧鍚庡垵綰ф墿銆佷腑綰ф墿絳夐兘緇ф壙鑷墿綾伙紙褰撶劧涓嶅悓縐嶇被鐨勫垯闇瑕佸彟鍒涘緩綾伙紝浣嗘槸妯″紡鐩稿悓錛夈傚湪 姣忎釜鍏沖崱錛屾垜浠兘瑕佸垱寤烘墿鐨勫疄渚嬶紝渚嬪鍒濈駭灝卞垱寤哄垵綰ф墿錛堟湁寰堝縐嶇被錛夈佷腑綰у垱寤轟腑綰ф墿絳夈傚彲浠ユ兂璞″湪榪欎釜緋葷粺涓紝灝嗕細鏈夋垚鍗冧笂涓囩殑鎬墿瀹炰緥瑕佸垱 寤猴紝闂鏄繕瑕佷繚璇佸垱寤虹殑鏃跺欎笉浼氬嚭閿欙細鍒濈駭涓嶈兘鍒涘緩BT綰х殑鎬墿錛堢帺瀹跺氨閮侀椃浜嗭紝鐜╁涓閮侀椃錛屾父鎴忎篃灝辨寕鎸備簡錛夛紝鍙嶄箣涔熶笉鍙互銆?/p>
AbstractFactory妯″紡灝辨槸鐢ㄦ潵瑙e喅榪欑被闂鐨勶細瑕佸垱寤轟竴緇勭浉鍏蟲垨鑰呯浉浜掍緷璧栫殑瀵硅薄銆?/p>
AbstractFactory妯″紡鍏稿瀷鐨勭粨鏋勫浘涓猴細
鍥?font face="Times New Roman">2-1錛?font face="Times New Roman">AbstractFactoryPattern緇撴瀯鍥?/p>
AbstractFactory妯″紡鍏抽敭灝辨槸灝嗚繖涓緇勫璞$殑鍒涘緩灝佽鍒頒竴涓敤浜庡垱寤哄璞$殑綾伙紙ConcreteFactory錛変腑錛岀淮鎶よ繖鏍蜂竴涓垱寤虹被鎬繪瘮緇存姢n澶氱浉鍏沖璞$殑鍒涘緩榪囩▼瑕佺畝鍗曠殑澶氥?/p>
AbstractFactory妯″紡鐨勫疄鐜版瘮杈冪畝鍗曪紝榪欓噷涓轟簡鏂逛究鍒濆鑰呯殑瀛︿範鍜屽弬鑰冿紝灝嗙粰鍑哄畬鏁寸殑瀹炵幇浠g爜錛堟墍鏈変唬鐮侀噰鐢?font face="Times New Roman">C++瀹炵幇錛屽茍鍦?font face="Times New Roman">VC 6.0涓嬫祴璇曡繍琛岋級銆?br>
浠g爜鐗囨柇1錛?/font>Product.h #ifndef _PRODUCT_H_ class AbstractProductA protected: private: }; class AbstractProductB protected: private: }; class ProductA1:public AbstractProductA ~ProductA1(); protected: private: }; class ProductA2:public AbstractProductA ~ProductA2(); protected: private: }; class ProductB1:public AbstractProductB ~ProductB1(); protected: private: }; class ProductB2:public AbstractProductB ~ProductB2(); protected: private: }; #endif //~_PRODUCT_H_ |
浠g爜鐗囨柇2錛?/font>Product.cpp #include "Product.h" #include <iostream> AbstractProductA::AbstractProductA() } AbstractProductA::~AbstractProductA() } AbstractProductB::AbstractProductB() } AbstractProductB::~AbstractProductB() } ProductA1::ProductA1() ProductA1::~ProductA1() } ProductA2::ProductA2() ProductA2::~ProductA2() } ProductB1::ProductB1() ProductB1::~ProductB1() } ProductB2::ProductB2() ProductB2::~ProductB2() } |
浠g爜鐗囨柇3錛?/font>AbstractFactory.h #ifndef _ABSTRACTFACTORY_H_ class AbstractProductA; class AbstractFactory virtual AbstractProductA* CreateProductA() = 0; virtual AbstractProductB* CreateProductB() = 0; protected: private: }; class ConcreteFactory1:public AbstractFactory ~ConcreteFactory1(); AbstractProductA* CreateProductA(); AbstractProductB* CreateProductB(); protected: private: }; class ConcreteFactory2:public AbstractFactory ~ConcreteFactory2(); AbstractProductA* CreateProductA(); AbstractProductB* CreateProductB(); protected: private: }; |
浠g爜鐗囨柇4錛?/font>AbstractFactory.cpp #include "AbstractFactory.h" #include <iostream> AbstractFactory::AbstractFactory() } AbstractFactory::~AbstractFactory() } ConcreteFactory1::ConcreteFactory1() } ConcreteFactory1::~ConcreteFactory1() } AbstractProductA* ConcreteFactory1::CreateProductA() AbstractProductB* ConcreteFactory1::CreateProductB() ConcreteFactory2::ConcreteFactory2() } ConcreteFactory2::~ConcreteFactory2() } AbstractProductA* ConcreteFactory2::CreateProductA() AbstractProductB* ConcreteFactory2::CreateProductB() |
浠g爜鐗囨柇5錛?/font>main.cpp #include "AbstractFactory.h" #include <iostream> int main(int argc,char* argv[]) cf1->CreateProductA(); AbstractFactory* cf2 = new ConcreteFactory2(); return 0; |
AbstractFactory妯″紡鐨勫疄鐜頒唬鐮佸緢綆鍗曪紝鍦ㄦ祴璇曠▼搴忎腑鍙互鐪嬪埌錛屽綋鎴戜滑瑕佸垱寤轟竴緇勫璞★紙ProductA1錛孭roductA2錛夌殑鏃跺欐垜浠彧鐢ㄧ淮鎶や竴涓垱寤哄璞★紙ConcreteFactory1錛夛紝澶уぇ綆鍖栦簡緇存姢鐨勬垚鏈拰宸ヤ綔銆?/font>
AbstractFactory妯″紡鍜孎actory妯″紡鐨勫尯鍒槸鍒濆錛堜嬌鐢級璁捐妯″紡鏃跺欑殑涓涓鏄撳紩璧峰洶鎯戠殑鍦版柟銆傚疄闄呬笂錛孉bstractFactory妯″紡鏄負鍒涘緩涓緇勶紙鏈夊綾伙級鐩稿叧鎴栦緷璧栫殑瀵硅薄鎻愪緵鍒涘緩鎺ュ彛錛岃孎actory妯″紡姝e鎴戝湪鐩稿簲鐨勬枃妗d腑鍒嗘瀽鐨勬槸涓?strong>涓綾?/strong>瀵硅薄鎻愪緵鍒涘緩鎺ュ彛鎴栧歡榪熷璞$殑鍒涘緩鍒板瓙綾諱腑瀹炵幇銆傚茍涓斿彲浠ョ湅鍒幫紝AbstractFactory妯″紡閫氬父閮芥槸浣跨敤Factory妯″紡瀹炵幇錛圕oncreteFactory1錛夈?/font>
鍏朵腑錛岃仛鍚堝叧緋伙紙Aggregation錛夛紝鍚堟垚鍏崇郴錛圕omposition錛夊睘浜庡叧鑱斿叧緋伙紙Association錛夈?/font>
涓鑸叧緋昏〃鐜頒負緇ф壙鎴栧疄鐜板叧緋?is a)錛屽叧鑱斿叧緋昏〃鐜頒負鍙橀噺(has a )錛屼緷璧栧叧緋昏〃鐜頒負鍑芥暟涓殑鍙傛暟(use a)銆?/font>
涓鑸寲鍏崇郴錛氳〃紺轟負綾諱笌綾諱箣闂寸殑緇ф壙鍏崇郴錛屾帴鍙d笌鎺ュ彛涔嬮棿鐨勭戶鎵匡紝綾誨鎺ュ彛鐨勫疄鐜板叧緋匯?br> 琛ㄧず鏂規硶錛?鐢ㄤ竴涓┖蹇冪澶達紜瀹炵嚎錛岀澶存寚鍚戠埗綾匯傛垨絀哄績綆ご錛嬭櫄綰匡紝濡傛灉鐖剁被鏄帴鍙c?/font>
鍏寵仈鍏崇郴錛氱被涓庣被涔嬮棿鐨勮仈鎺ワ紝瀹冧嬌涓涓被鐭ラ亾鍙︿竴涓被鐨勫睘鎬у拰鏂規硶銆?br> 琛ㄧず鏂規硶錛氱敤 瀹炵嚎錛嬬澶達紝 綆ご鎸囧悜琚嬌鐢ㄧ殑綾匯?/font>
鑱氬悎鍏崇郴錛氭槸鍏寵仈鍏崇郴鐨勪竴縐嶏紝鏄己鐨勫叧鑱斿叧緋匯傝仛鍚堝叧緋繪槸鏁翠綋鍜屼釜浣撶殑鍏崇郴銆傚叧鑱斿叧緋葷殑涓や釜綾誨浜庡悓涓灞傛涓婏紝鍟婅仛鍚堝叧緋諱袱涓被澶勪簬涓嶅悓鐨勫眰嬈★紝涓涓槸鏁翠綋錛屼竴涓槸閮ㄥ垎銆?br> 琛ㄧず鏂規硶錛氱┖蹇冭彵褰紜瀹炵嚎錛嬬澶達紝綆ご鎸囧悜閮ㄥ垎銆?/font>
鍚堟垚鍏崇郴錛氭槸鍏寵仈鍏崇郴鐨勪竴縐嶏紝鏄瘮鑱氬悎鍏崇郴寮虹殑鍏崇郴銆傚畠瑕佹眰鏅氱殑鑱氬悎鍏崇郴涓唬琛ㄦ暣浣撶殑瀵硅薄璐熻矗浠h〃閮ㄥ垎鐨勫璞$殑鐢熷懡鍛ㄦ湡錛屽悎鎴愬叧緋諱笉鑳藉叡浜?br> 琛ㄧず鏂規硶錛氬疄蹇冭彵褰紜瀹炵嚎錛嬬澶達紝
渚濊禆鍏崇郴錛氭槸綾諱笌綾諱箣闂寸殑榪炴帴錛岃〃紺轟竴涓被渚濊禆浜庡彟涓涓被鐨勫畾涔夈備緥濡傚鏋淎渚濊禆浜嶣錛屽垯B浣撶幇涓哄眬閮ㄥ彉閲忥紝鏂規硶鐨勫弬鏁般佹垨闈欐佹柟娉曠殑璋冪敤銆?br> 琛ㄧず鏂規硶錛氳櫄綰匡紜綆ご
鏈枃妗g粰鍑鴻璁℃ā寮忎箣鈥斺擮bserver妯″紡鐨勭畝鍖栬癄閲婏紝騫剁粰鍑哄叾C++瀹炵幇銆?/p>
Project |
Design Pattern Explanation錛圔y K_Eckel錛?/p> |
Authorization |
Free Distributed but Ownership Reserved |
Date |
|
Test Bed |
MS Visual C++ 6.0 |
鍦ㄦ湰鏂囨。鐨勫啓浣滀腑錛屽弬鑰冧簡浠ヤ笅鐨勮祫婧愶紝鍦ㄦ鍒楀嚭琛ㄧず鎰熻阿錛?/p>
u 涔︾睄
[GoF 2000]錛欸oF,Design Patterns-Elements of Reusable Object-Oriented Software
Addison-Wesley 2000/9.
[Martine 2003]錛歊obert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.
Author |
K_Eckel |
State |
Candidate for Master’s Degree School of |
E_mail |
Observer妯″紡搴旇鍙互璇存槸搴旂敤鏈澶氥佸獎鍝嶆渶騫跨殑妯″紡涔嬩竴錛屽洜涓篛bserver鐨勪竴涓疄渚婱odel/View/Control錛圡VC錛夌粨鏋勫湪緋葷粺寮鍙戞灦鏋勮璁′腑鏈夌潃寰堥噸瑕佺殑鍦頒綅鍜屾剰涔夛紝MVC瀹炵幇浜嗕笟鍔¢昏緫鍜岃〃紺哄眰鐨勮В鑰︺?strong>涓漢涔熻涓?/strong>Observer妯″紡鏄蔣浠跺紑鍙戣繃紼嬩腑蹇呴』瑕佹帉鎻″拰浣跨敤鐨勬ā寮忎箣涓銆傚湪MFC涓紝Doc/View錛堟枃妗h鍥劇粨鏋勶級鎻愪緵浜嗗疄鐜癕VC鐨勬鏋剁粨鏋勶紙鏈変竴涓粠璁捐妯″紡錛圤bserver妯″紡錛夌殑瑙掑害鍒嗘瀽鍒嗘瀽Doc/View鐨勬枃绔犳鍦ㄨ繘涓姝ョ殑鎾板啓褰撲腑錛岄仐鎲劇殑鏄椂闂達細錛夛級銆傚湪Java闃靛涓紝Struts鍒欐彁渚涘拰MFC涓璂oc/View緇撴瀯綾諱技鐨勫疄鐜癕VC鐨勬鏋躲傚彟澶朖ava璇█鏈韓灝辨彁渚涗簡Observer妯″紡鐨勫疄鐜版帴鍙o紝榪欏皢鍦ㄨ璁轟腑緇欏嚭銆?/p>
褰撶劧錛孧VC鍙槸Observer妯″紡鐨勪竴涓疄渚嬨侽bserver妯″紡瑕佽В鍐崇殑闂涓猴細寤虹珛涓涓竴錛圫ubject錛夊澶氾紙Observer錛? 鐨勪緷璧栧叧緋伙紝騫朵笖鍋氬埌褰?#8220;涓”鍙樺寲鐨勬椂鍊欙紝渚濊禆榪欎釜“涓”鐨勫涔熻兘澶熷悓姝ユ敼鍙樸傛渶甯歌鐨勪竴涓緥瀛愬氨鏄細瀵瑰悓涓緇勬暟鎹繘琛岀粺璁″垎鏋愭椂鍊欙紝鎴戜滑甯屾湜鑳藉鎻愪緵澶? 縐嶅艦寮忕殑琛ㄧず錛堜緥濡備互琛ㄦ牸榪涜緇熻鏄劇ず銆佹煴鐘跺浘緇熻鏄劇ず銆佺櫨鍒嗘瘮緇熻鏄劇ず絳夛級銆傝繖浜涜〃紺洪兘渚濊禆浜庡悓涓緇勬暟鎹紝鎴戜滑褰撶劧闇瑕佸綋鏁版嵁鏀瑰彉鐨勬椂鍊欙紝鎵鏈夌殑緇熻鐨? 鏄劇ず閮借兘澶熷悓鏃舵敼鍙樸侽bserver妯″紡灝辨槸瑙e喅浜嗚繖涓涓棶棰樸?/p>
Observer妯″紡鍏稿瀷鐨勭粨鏋勫浘涓猴細
鍥?font face="Times New Roman">2-1錛?font face="Times New Roman">Observer Pattern緇撴瀯鍥?/p>
榪欓噷鐨勭洰鏍?font face="Times New Roman">Subject鎻愪緵渚濊禆浜庡畠鐨勮瀵熻?font face="Times New Roman">Observer鐨勬敞鍐岋紙Attach錛夊拰娉ㄩ攢錛?font face="Times New Roman">Detach錛夋搷浣滐紝騫朵笖鎻愪緵浜嗕嬌寰椾緷璧栦簬瀹冪殑鎵鏈夎瀵熻呭悓姝ョ殑鎿嶄綔錛?font face="Times New Roman">Notify錛夈傝瀵熻?font face="Times New Roman">Observer鍒欐彁渚涗竴涓?font face="Times New Roman">Update鎿嶄綔錛屾敞鎰忚繖閲岀殑Observer鐨?font face="Times New Roman">Update鎿嶄綔騫朵笉鍦?font face="Times New Roman">Observer鏀瑰彉浜?font face="Times New Roman">Subject鐩爣鐘舵佺殑鏃跺欏氨瀵硅嚜宸辮繘琛屾洿鏂幫紝榪欎釜鏇存柊鎿嶄綔瑕佸歡榪熷埌Subject瀵硅薄鍙戝嚭Notify閫氱煡鎵鏈?font face="Times New Roman">Observer榪涜淇敼錛堣皟鐢?font face="Times New Roman">Update錛夈?/p>
Observer妯″紡鐨勫疄鐜版湁浜涚壒鐐癸紝榪欓噷涓轟簡鏂逛究鍒濆鑰呯殑瀛︿範鍜屽弬鑰冿紝灝嗙粰鍑哄畬鏁寸殑瀹炵幇浠g爜錛堟墍鏈変唬鐮侀噰鐢?font face="Times New Roman">C++瀹炵幇錛屽茍鍦?font face="Times New Roman">VC 6.0涓嬫祴璇曡繍琛岋級銆?br>
浠g爜鐗囨柇1錛?/font>Subject.h #ifndef _SUBJECT_H_ #include <list> typedef string State; class Observer; class Subject virtual void Attach(Observer* obv); virtual void Detach(Observer* obv); virtual void Notify(); virtual void SetState(const State& st) = 0; virtual State GetState() = 0; protected: private: }; class ConcreteSubject:public Subject ~ConcreteSubject(); State GetState(); void SetState(const State& st); protected: private: }; #endif //~_SUBJECT_H_ |
浠g爜鐗囨柇2錛?/font>Subject.cpp #include "Subject.h" #include <iostream> typedef string state; Subject::Subject() } Subject::~Subject() void Subject::Attach(Observer* obv) void Subject::Detach(Observer* obv) void Subject::Notify() it = _obvs->begin(); for (;it != _obvs->end();it++) (*it)->Update(this); ConcreteSubject::ConcreteSubject() ConcreteSubject::~ConcreteSubject()
void ConcreteSubject::SetState(const State& st) |
浠g爜鐗囨柇3錛?/font>Observer.h #ifndef _OBSERVER_H_ #include "Subject.h" #include <string> typedef string State; class Observer virtual void Update(Subject* sub) = 0; virtual void PrintInfo() = 0; protected: State _st; private: }; class ConcreteObserverA:public Observer virtual ~ConcreteObserverA(); //浼犲叆Subject浣滀負鍙傛暟錛岃繖鏍峰彲浠ヨ涓涓?/font>View灞炰簬澶氫釜鐨?/font>Subject銆?/font> void PrintInfo(); protected: private: }; class ConcreteObserverB:public Observer virtual ~ConcreteObserverB(); //浼犲叆Subject浣滀負鍙傛暟錛岃繖鏍峰彲浠ヨ涓涓?/font>View灞炰簬澶氫釜鐨?/font>Subject銆?/font> void PrintInfo(); protected: private: }; #endif //~_OBSERVER_H_ |
浠g爜鐗囨柇4錛?/font>Observer.cpp #include "Observer.h" #include <iostream> Observer::Observer() } Observer::~Observer() }
_sub->Attach(this); ConcreteObserverA::~ConcreteObserverA() if (_sub != 0) Subject* ConcreteObserverA::GetSubject() void ConcreteObserverA::PrintInfo() void ConcreteObserverA::Update(Subject* sub) PrintInfo(); ConcreteObserverB::ConcreteObserverB(Subject* sub) _sub->Attach(this); ConcreteObserverB::~ConcreteObserverB() if (_sub != 0) Subject* ConcreteObserverB::GetSubject() void ConcreteObserverB::PrintInfo() void ConcreteObserverB::Update(Subject* sub) PrintInfo(); |
浠g爜鐗囨柇5錛?/font>main.cpp #include "Subject.h" #include <iostream> int main(int argc,char* argv[]) Observer* o1 = new ConcreteObserverA(sub); Observer* o2 = new ConcreteObserverB(sub); sub->SetState("old"); sub->Notify(); sub->SetState("new"); //涔熷彲浠ョ敱Observer璋冪敤 sub->Notify(); return 0; |
鍦∣bserver妯″紡鐨勫疄鐜頒腑錛孲ubject緇存姢涓涓猯ist浣滀負瀛樺偍鍏舵墍鏈夎瀵熻呯殑瀹瑰櫒銆傛瘡褰撹皟鐢∟otify鎿嶄綔灝遍亶鍘唋ist涓殑Observer瀵硅薄錛屽茍騫挎挱閫氱煡鏀瑰彉鐘舵侊紙璋冪敤Observer鐨刄pdate鎿嶄綔錛夈傜洰鏍囩殑鐘舵乻tate鍙互鐢盨ubject鑷繁鏀瑰彉錛堢ず渚嬶級錛屼篃鍙互鐢監bserver鐨勬煇涓搷浣滃紩璧穝tate鐨勬敼鍙橈紙鍙皟鐢⊿ubject鐨凷etState鎿嶄綔錛夈侼otify鎿嶄綔鍙互鐢盨ubject鐩爣涓誨姩騫挎挱錛堢ず渚嬶級錛屼篃鍙互鐢監bserver瑙傚療鑰呮潵璋冪敤錛堝洜涓篛bserver緇存姢涓涓寚鍚慡ubject鐨勬寚閽堬級銆?/font>
榪愯紺轟緥紼嬪簭錛屽彲浠ョ湅鍒板綋Subject澶勪簬鐘舵?#8220;old”鏃跺欙紝渚濊禆浜庡畠鐨勪袱涓瀵熻呴兘鏄劇ず“old”錛屽綋鐩爣鐘舵佹敼鍙樹負“new”鐨勬椂鍊欙紝渚濊禆浜庡畠鐨勪袱涓瀵熻呬篃閮芥敼鍙樹負“new”銆?/font>
Observer鏄獎鍝嶆瀬涓烘繁榪滅殑妯″紡涔嬩竴錛屼篃鏄湪澶у瀷緋葷粺寮鍙戣繃紼嬩腑瑕佺敤鍒扮殑妯″紡涔嬩竴銆傞櫎浜哅FC銆丼truts鎻愪緵浜哅VC鐨勫疄鐜版鏋訛紝鍦↗ava璇█涓繕鎻愪緵浜嗕笓闂ㄧ殑鎺ュ彛瀹炵幇Observer妯″紡錛氶氳繃涓撻棬鐨勭被Observable鍙奜bserver鎺ュ彛鏉ュ疄鐜癕VC緙栫▼妯″紡錛屽叾UML鍥懼彲浠ヨ〃紺轟負錛?/font>
Java涓疄鐜癕VC鐨刄ML鍥俱?/font>
榪欓噷鐨凮bserver灝辨槸瑙傚療鑰咃紝Observable鍒欏厖褰撶洰鏍嘢ubject鐨勮鑹層?/font>
Observer妯″紡涔熺О涓哄彂甯冿紞璁㈤槄錛坧ublish-subscribe錛夛紝鐩爣灝辨槸閫氱煡鐨勫彂甯冭咃紝瑙傚療鑰呭垯鏄氱煡鐨勮闃呰咃紙鎺ュ彈閫氱煡錛夈?/font>
Definition:
A class is a pure interface if it meets the following requirements:
= 0
") methods
and static methods (but see below for destructor).
Interface
suffix.
An interface class can never be directly instantiated because of the pure virtual method(s) it declares. To make sure all implementations of the interface can be destroyed correctly, they must also declare a virtual destructor (in an exception to the first rule, this should not be pure). See Stroustrup, The C++ Programming Language, 3rd edition, section 12.4 for details.
Pros:
Tagging a class with the Interface
suffix lets
others know that they must not add implemented methods or non
static data members. This is particularly important in the case of
multiple inheritance.
Additionally, the interface concept is already well-understood by
Java programmers.
Cons:
The Interface
suffix lengthens the class name, which
can make it harder to read and understand. Also, the interface
property may be considered an implementation detail that shouldn't
be exposed to clients.
Decision:
A class may end with Interface
only if it meets the
above requirements. We do not require the converse, however:
classes that meet the above requirements are not required to end
with Interface
.