📄 pat5g.htm
字号:
subject is in an inconsistent state:</P><A NAME="auto1058"></A><PRE> void MySubject::Operation (int newValue) { BaseClassSubject::Operation(newValue); // trigger notification _myInstVar += newValue; // update subclass state (too late!) }</PRE><A NAME="auto1059"></A><P>You can avoid this pitfall by sending notifications from templatemethods (<A HREF="pat5jfs.htm" TARGET="_mainDisplayFrame">Template Method (325)</A>) in abstract Subjectclasses. Define a primitive operation for subclasses to override, and makeNotify the last operation in the template method, which will ensure thatthe object is self-consistent when subclasses override Subjectoperations.</P><A NAME="auto1060"></A><PRE> void Text::Cut (TextRange r) { ReplaceRange(r); // redefined in subclasses Notify(); }</PRE><A NAME="auto1061"></A><P>By the way, it's always a good idea to document which Subject operationstrigger notifications.</P></LI><A NAME="auto1062"></A><P></P><A NAME="auto1063"></A><LI><EM>Avoiding observer-specific update protocols: the pushand pull models.</EM> Implementations of the Observer pattern often havethe subject broadcast additional information about the change. Thesubject passes this information as an argument to Update. The amountof information may vary widely.<A NAME="pullmodel"></A><A NAME="pushmodel"></A><P>At one extreme, which we call the <STRONG>push model</STRONG>, the subjectsends observers detailed information about the change, whether theywant it or not. At the other extreme is the <STRONG>pull model</STRONG>;the subject sends nothing but the most minimal notification, andobservers ask for details explicitly thereafter.</P><A NAME="auto1064"></A><P>The pull model emphasizes the subject's ignorance of its observers,whereas the push model assumes subjects know something about theirobservers' needs. The push model might make observers less reusable,because Subject classes make assumptions about Observer classes thatmight not always be true. On the other hand, the pull model may beinefficient, because Observer classes must ascertain what changedwithout help from the Subject.</P></LI><A NAME="auto1065"></A><P></P><A NAME="aspect"></A><LI><EM>Specifying modifications of interest explicitly.</EM>You can improve update efficiency by extending the subject'sregistration interface to allow registering observers only forspecific events of interest. When such an event occurs, the subjectinforms only those observers that have registered interest in thatevent. One way to support this uses the notion of<STRONG>aspects</STRONG> for Subject objects. To registerinterest in particular events, observers are attached to theirsubjects using<A NAME="auto1066"></A><PRE> void Subject::Attach(Observer*, Aspect& interest);</PRE><A NAME="auto1067"></A><P>where <CODE>interest</CODE> specifies the eventof interest. At notification time, the subject supplies the changedaspect to its observers as a parameter to the Update operation. Forexample:</P><A NAME="auto1068"></A><PRE> void Observer::Update(Subject*, Aspect& interest);</PRE></LI><A NAME="auto1069"></A><P></P><A NAME="depend-manage"></A><A NAME="encap-complex"></A><A NAME="media-w-obsrv"></A><LI><EM>Encapsulating complex update semantics.</EM>When the dependency relationship between subjects and observers isparticularly complex, an object that maintains these relationships mightbe required. We call such an object a <STRONG>ChangeManager</STRONG>. Itspurpose is to minimize the work required to make observers reflect achange in their subject. For example, if an operation involveschanges to several interdependent subjects, you might have toensure that their observers are notified only after <EM>all</EM> thesubjects have been modified to avoid notifying observers more thanonce.<A NAME="auto1070"></A><P>ChangeManager has three responsibilities:</P><OL><A NAME="auto1071"></A><LI>It maps a subject to its observers and provides an interface tomaintain this mapping. This eliminates the need for subjects to maintainreferences to their observers and vice versa.</LI><A NAME="auto1072"></A><P></P><A NAME="auto1073"></A><LI>It defines a particular update strategy.</LI><A NAME="auto1074"></A><P></P><A NAME="auto1075"></A><LI>It updates all dependent observers at the request of a subject.</LI></OL><A NAME="auto1076"></A><P>The following diagram depicts a simple ChangeManager-based implementation ofthe Observer pattern. There are two specialized ChangeManagers.SimpleChangeManager is naive in that it always updates all observers ofeach subject. In contrast, DAGChangeManager handles directed-acyclicgraphs of dependencies between subjects and their observers. ADAGChangeManager is preferable to a SimpleChangeManager when an observerobserves more than one subject. In that case, a change in two or moresubjects might cause redundant updates. The DAGChangeManager ensuresthe observer receives just one update. SimpleChangeManager isfine when multiple updates aren't an issue.</P><A NAME="subj-300c"></A><P ALIGN=CENTER><IMG SRC="Pictures/obser025.gif"></P><A NAME="auto1077"></A><P>ChangeManager is an instance of the <A HREF="pat5efs.htm" TARGET="_mainDisplayFrame">Mediator (273)</A>pattern. In general there is only one ChangeManager, and it is knownglobally. The <A HREF="pat3efs.htm" TARGET="_mainDisplayFrame">Singleton (127)</A> pattern would beuseful here.</P></LI><A NAME="auto1078"></A><P></P><A NAME="subj-w-obser"></A><LI><EM>Combining the Subject and Observer classes.</EM>Class libraries written in languages that lack multiple inheritance(like Smalltalk) generally don't define separate Subject and Observerclasses but combine their interfaces in one class. That lets youdefine an object that acts as both a subject and an observer withoutmultiple inheritance. In Smalltalk, for example, the Subject andObserver interfaces are defined in the root class Object, making themavailable to all classes.</LI></OL><A NAME="samplecode"><A><H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Known Uses"></A> Sample Code</H2> <A NAME="auto1079"></A><P>An abstract class defines the Observer interface:</P><A NAME="auto1080"></A><PRE> class Subject; class Observer { public: virtual ~ Observer(); virtual void Update(Subject* theChangedSubject) = 0; protected: Observer(); };</PRE><A NAME="auto1081"></A><P>This implementation supports multiple subjects for each observer. Thesubject passed to the <CODE>Update</CODE> operation lets the observerdetermine which subject changed when it observes more than one.</P><A NAME="subject"></A><P>Similarly, an abstract class defines the Subject interface:</P><A NAME="auto1082"></A><PRE> class Subject { public: virtual ~Subject(); virtual void Attach(Observer*); virtual void Detach(Observer*); virtual void Notify(); protected: Subject(); private: List<Observer*> *_observers; }; void Subject::Attach (Observer* o) { _observers->Append(o); } void Subject::Detach (Observer* o) { _observers->Remove(o); } void Subject::Notify () { ListIterator<Observer*> i(_observers); for (i.First(); !i.IsDone(); i.Next()) { i.CurrentItem()->Update(this); } }</PRE><A NAME="auto1083"></A><P><CODE>ClockTimer</CODE> is a concrete subject for storing andmaintaining the time of day. It notifies its observers every second.<CODE>ClockTimer</CODE> provides the interface for retrieving individualtime units such as the hour, minute, and second.</P><A NAME="auto1084"></A><PRE> class ClockTimer : public Subject { public: ClockTimer(); virtual int GetHour(); virtual int GetMinute(); virtual int GetSecond(); void Tick(); };</PRE><A NAME="auto1085"></A><P>The <CODE>Tick</CODE> operation gets called by an internal timer atregular intervals to provide an accurate time base. <CODE>Tick</CODE>updates the <CODE>ClockTimer</CODE>'s internal state and calls<CODE>Notify</CODE> to inform observers of the change:</P><A NAME="auto1086"></A><PRE> void ClockTimer::Tick () { // update internal time-keeping state // ... Notify(); }</PRE><A NAME="digitalclock"></A><P>Now we can define a class <CODE>DigitalClock</CODE> that displays thetime. It inherits its graphical functionality from a <CODE>Widget</CODE>class provided by a user interface toolkit. The Observer interface ismixed into the <CODE>DigitalClock</CODE> interface by inheriting from<CODE>Observer</CODE>.</P><A NAME="auto1087"></A><PRE> class DigitalClock: public Widget, public Observer { public: DigitalClock(ClockTimer*); virtual ~DigitalClock(); virtual void Update(Subject*); // overrides Observer operation virtual void Draw(); // overrides Widget operation; // defines how to draw the digital clock private: ClockTimer* _subject; }; DigitalClock::DigitalClock (ClockTimer* s) { _subject = s; _subject->Attach(this); } DigitalClock:: DigitalClock () { _subject->Detach(this); }</PRE><A NAME="auto1088"></A><P>Before the <CODE>Update</CODE> operation draws the clock face, it checksto make sure the notifying subject is the clock's subject:</P><A NAME="auto1089"></A><PRE> void DigitalClock::Update (Subject* theChangedSubject) { if (theChangedSubject == _subject) { Draw(); } } void DigitalClock::Draw () { // get the new values from the subject int hour = _subject->GetHour(); int minute = _subject->GetMinute(); // etc. // draw the digital clock }</PRE><A NAME="clock"></A><P>An <CODE>AnalogClock</CODE> class can be defined in the same way.</P><A NAME="auto1090"></A><PRE> class AnalogClock : public Widget, public Observer { public: AnalogClock(ClockTimer*); virtual void Update(Subject*); virtual void Draw(); // ... };</PRE><A NAME="auto1091"></A><P>The following code creates an <CODE>AnalogClock</CODE> and a<CODE>DigitalClock</CODE> that always show the same time:</P><A NAME="auto1092"></A><PRE> ClockTimer* timer = new ClockTimer; AnalogClock* analogClock = new AnalogClock(timer); DigitalClock* digitalClock = new DigitalClock(timer);</PRE><A NAME="auto1093"></A><P>Whenever the <CODE>timer</CODE> ticks, the two clocks will be updatedand will redisplay themselves appropriately.</P><A NAME="knownuses"><A><H2><A HREF="#relatedpatterns"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Related Patterns"></A> Known Uses</H2> <A NAME="smalltalk-use-observe"></A><A NAME="think-use-obsrv"></A><P>The first and perhaps best-known example of the Observer pattern appearsin Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalkenvironment [<A HREF="bibfs.htm#krasner_mvc" TARGET="_mainDisplayFrame">KP88</A>]. MVC's Model class plays the role ofSubject, while View is the base class for observers. Smalltalk,ET++ [<A HREF="bibfs.htm#et++" TARGET="_mainDisplayFrame">WGM88</A>], and the THINK class library [<A HREF="bibfs.htm#think" TARGET="_mainDisplayFrame">Sym93b</A>] provide ageneral dependency mechanism by putting Subject and Observer interfacesin the parent class for all other classes in the system.</P><A NAME="toolkit"></A><A NAME="unidraw-use-observ"></A><P>Other user interface toolkits that employ this pattern areInterViews [<A HREF="bibfs.htm#interviews_composition" TARGET="_mainDisplayFrame">LVC89</A>], the AndrewToolkit [<A HREF="bibfs.htm#atk" TARGET="_mainDisplayFrame">P+88</A>], and Unidraw [<A HREF="bibfs.htm#unidraw_framework" TARGET="_mainDisplayFrame">VL90</A>]. InterViewsdefines Observer and Observable (for subjects) classes explicitly.Andrew calls them "view" and "data object," respectively. Unidrawsplits graphical editor objects into View (for observers) and Subjectparts.</P><A NAME="relatedpatterns"></A><H2><A HREF="#last"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: navigation"></A> Related Patterns</H2> <A NAME="auto1094"></A><P><A HREF="pat5efs.htm" TARGET="_mainDisplayFrame">Mediator (273)</A>: Byencapsulating complex update semantics, the ChangeManager acts asmediator between subjects and observers.</P><A NAME="auto1095"></A><P><A HREF="pat3efs.htm" TARGET="_mainDisplayFrame">Singleton (127)</A>:The ChangeManager may use the Singleton pattern to make it uniqueand globally accessible.</P><A NAME="last"></A><P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR><A HREF="pat5hfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5hfs.htm" TARGET="_mainDisplayFrame">State</A><BR><A HREF="pat5ffs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif" ALIGN=TOP BORDER=0></A> <A HREF="pat5ffs.htm" TARGET="_mainDisplayFrame">Memento</A></P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -