Callbacks (Bruce Eckel's Thinking in C++, 2nd Ed)

Callbacks

Decouplingcode behavior

Functor

Command

Strategy

Observer

Likethe other forms of callback: a hook point where you can change code. Differenceis in observer’s completely dynamic nature. Often used for the specificcase of changes based on other object’s change of state, but is also thebasis of event management. Anytime you want to decouple the source of the callfrom the called code in a completely dynamic way.
Theobserverpattern solves a fairly common problem: What if a group of objects needs toupdate themselves when some other object changes state? This can be seen in the“model-view” aspect of Smalltalk’s MVC(model-view-controller), or the almost-equivalent “Document-ViewArchitecture.” Suppose that you have some data (the“document”) and more than one view, say a plot and a textual view.When you change the data, the two views must know to update themselves, andthat’s what the observer facilitates.
Thereare two types of objects used to implement the observer pattern in thefollowing code. The Observable class keeps track of everybody who wants to be informed when a change happens,whether the “state” has changed or not. When someone says“OK, everybody should check and potentially update themselves,” the Observable class performs this task by calling the notifyObservers( ) member function for each observer on the list. The notifyObservers( ) member function is part of the base class Observable .
Thereare actually two “things that change” in the observer pattern: thequantity of observing objects and the way an update occurs. That is, theobserver pattern allows you to modify both of these without affecting thesurrounding code.
Thereare a number of ways to implement the observer pattern, but the code shown herewill create a framework from which you can build your own observer code,following the example. First, this interface describes what an observer lookslike:
//: C25:Observer.h
// The Observer interface
#ifndef OBSERVER_H
#define OBSERVER_H

class Observable;
class Argument {};

class Observer {
public:
  // Called by the observed object, whenever 
  // the observed object is changed:
  virtual void 
  update(Observable* o, Argument * arg) = 0;
};
#endif // OBSERVER_H ///:~ 
Since Observer interacts with Observable in this approach, Observable must be declared first. In addition, the Argument class is empty and only acts as a base class for any type of argument you wishto pass during an update. If you want, you can simply pass the extra argumentas a void* ;you’ll have to downcast in either case but some folks find void* objectionable.
Observer is an “interface” class that only has one member function, update( ) .This function is called by the object that’s being observed, when thatobject decides its time to update all it’s observers. The arguments areoptional; you could have an update( ) with no arguments and that would still fit the observer pattern; however thisis more general – it allows the observed object to pass the object thatcaused the update (since an Observer maybe registered with more than one observed object) and any extra information ifthat’s helpful, rather than forcing the Observer object to hunt around to see who is updating and to fetch any other informationit needs.
The“observed object” that decides when and how to do the updating willbe called the Observable :
//: C25:Observable.h
// The Observable class
#ifndef OBSERVABLE_H
#define OBSERVABLE_H
#include <set>
#include "Observer.h"

class Observable {
  bool changed;
  std::set<Observer*> observers;
protected:
  virtual void setChanged() { changed = true; }
  virtual void clearChanged(){ changed = false; }
public:
  virtual void addObserver(Observer& o) {
    observers.insert(&o);
  }
  virtual void deleteObserver(Observer& o) {
    observers.erase(&o);
  }
  virtual void deleteObservers() {
    observers.clear();
  }
  virtual int countObservers() {
    return observers.size();
  }
  virtual bool hasChanged() { return changed; }
  // If this object has changed, notify all
  // of its observers:
  virtual void notifyObservers(Argument* arg=0) {
    if(!hasChanged()) return;
    std::set<Observer*>::iterator it;
    for(it = observers.begin(); 
      it != observers.end(); it++)
      (*it)->update(this, arg);
    clearChanged(); // Not "changed" anymore
  }
};
#endif // OBSERVABLE_H ///:~ 
Again,the design here is more elaborate than is necessary; as long as there’s away to register an Observer with an Observable and for the Observable to update its Observer s,the set of member functions doesn’t matter. However, this design isintended to be reusable (it was lifted from the design used in the Javastandard library). As mentioned elsewhere in the book, there is no support formultithreading in the Standard C++ libraries, so this design would need to bemodified in a multithreaded environment.
Observable has a flag to indicate whether it’s been changed. In a simpler design,there would be no flag; if something happened, everyone would be notified. Theflag allows you to wait, and only notify the Observer swhen you decide the time is right. Notice, however, that the control of theflag’s state is protected ,so that only an inheritor can decide what constitutes a change, and not the enduser of the resulting derived Observer class.
Thecollection of Observer objects is kept in a set<Observer*> to prevent duplicates; the setinsert( ) , erase( ) , clear( ) and size( ) functions are exposed to allow Observer sto be added and removed at any time, thus providing run-time flexibility.
Mostof the work is done in notifyObservers( ) .If the changed flag has not been set, this does nothing. Otherwise, it moves through the set and calls back to the update( ) member function of each Observer .Finally, it clears the changed flag so repeated calls to notifyObservers( ) won’t waste time.
Atfirst it may appear that you can use an ordinary Observable object to manage the updates. But this doesn’t work; to get an effect, you must inherit from Observable and somewhere in your derived-class code call setChanged( ) .This is the member function that sets the “changed” flag, whichmeans that when you call notifyObservers( ) all of the observers will, in fact, get notified. Where you call setChanged( ) depends on the logic of your program.
Nowwe encounter a dilemma. An object that should notify its observers about thingsthat happen to it – events or changes in state – might have morethan one such item of interest. For example, if you’re dealing with agraphical user interface (GUI) item – a button, say – the items ofinterest might be the mouse clicked the button, the mouse moved over thebutton, and (for some reason) the button changed its color. So we’d liketo be able to report all of these events to different observers, each of whichis interested in a different type of event.
Theproblem is that we would normally reach for multiple inheritance in such asituation: “I’ll inherit from Observable to deal with mouse clicks, and I’ll ... er ... inherit from Observable to deal with mouse-overs, and, well, ... hmm, that doesn’t work.”
The“interface” idiom
The“inner class” idiom
Here’sa situation where we do actually need to (in effect) upcast to more than onetype, but in this case we need to provide several different implementations of the same base type. The solution is something I’velifted from Java, which takes C++’s nested class one step further. Javahas a built-in feature called innerclasses ,which look like C++’s nested classes, but they do two other things:
  1. AJava inner class automatically has access to the private elements of the classit is nested within.
  2. Anobject of a Java inner class automatically grabs the “this” to theouter class object it was created within. In Java, the “outer this”is implicitly dereferenced whenever you name an element of the outer class.
Soto implement the inner class idiom in C++, we must do these things by hand.Here’s an example:
//: C25:InnerClassIdiom.cpp
// Example of the "inner class" idiom
#include <iostream>
#include <string>
using namespace std;

class Poingable {
public:
  virtual void poing() = 0;
};

void callPoing(Poingable& p) {
  p.poing();
}

class Bingable {
public:
  virtual void bing() = 0;
};

void callBing(Bingable& b) {
  b.bing();
}

class Outer {
  string name;
  // Define one inner class:
  class Inner1;
  friend class Outer::Inner1;
  class Inner1 : public Poingable {
    Outer* parent;
  public:
    Inner1(Outer* p) : parent(p) {}
    void poing() {
      cout << "poing called for "
        << parent->name << endl;
      // Accesses data in the outer class object
    }
  } inner1;
  // Define a second inner class:
  class Inner2;
  friend class Outer::Inner2;
  class Inner2 : public Bingable {
    Outer* parent;
  public:
    Inner2(Outer* p) : parent(p) {}
    void bing() {
      cout << "bing called for "
        << parent->name << endl;
    }
  } inner2;
public:
  Outer(const string& nm) : name(nm), 
    inner1(this), inner2(this) {}
  // Return reference to interfaces
  //  implemented by the inner classes:
  operator Poingable&() { return inner1; }
  operator Bingable&() { return inner2; }
};

int main() {
  Outer x("Ping Pong");
  // Like upcasting to multiple base types!:
  callPoing(x);
  callBing(x);
} ///:~ 
Theexample begins with the Poingable and Bingable interfaces,each of which contain a single member function. The services provided by callPoing( ) and callBing( ) requirethat the object they receive implement the Poingable and Bingable interfaces,respectively, but they put no other requirements on that object so as tomaximize the flexibility of using callPoing( ) and callBing( ) .Note the lack of virtual destructors in either interface – the intent is that you never performobject destruction via the interface.
Outer contains some private data ( name )and it wishes to provide both a Poingable interface and a Bingable interfaceso it can be used with callPoing( ) and callBing( ) .Of course, in this situation we could simply use multiple inheritance. This example is just intended to show thesimplest syntax for the idiom; we’ll see a real use shortly. To provide a Poingable object without inheriting Outer from Poingable ,the inner class idiom is used. First, the declaration classInner says that, somewhere, there is a nested class of this name. This allows the friend declaration for the class, which follows. Finally, now that the nested classhas been granted access to all the private elements of Outer ,the class can be defined. Notice that it keeps a pointer to the Outer which created it, and this pointer must be initialized in the constructor.Finally, the poing( ) function from Poingable is implemented. The same process occurs for the second inner class whichimplements Bingable .Each inner class has a single private instance created, which is initialized in the Outer constructor. By creating the member objects and returning references to them,issues of object lifetime are eliminated.
Noticethat both inner class definitions are private ,and in fact the client programmer doesn’t have any access to details ofthe implementation, since the two access methods operatorPoingable&( ) and operatorBingable&( ) only return a reference to the upcast interface, not to the object thatimplements it. In fact, since the two inner classes are private ,the client programmer cannot even downcast to the implementation classes, thusproviding complete isolation between interface and implementation.
Justto push a point, I’ve taken the extra liberty here of defining theautomatic type conversion operators operatorPoingable&( ) and operatorBingable&( ) .In main( ) ,you can see that these actually allow a syntax that looks like Outer is multiply inherited from Poingable and Bingable .The difference is that the casts in this case are one way. You can get theeffect of an upcast to Poingable or Bingable ,but you cannot downcast back to an Outer .In the following example of observer, you’ll see the more typicalapproach: you provide access to the inner class objects using ordinary memberfunctions, not automatic type conversion operations.
Theobserver example
Armedwith the Observer and Observable headerfiles and the inner class idiom, we can look at an example of the observerpattern:
//: C25:ObservedFlower.cpp
// Demonstration of "observer" pattern
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include "Observable.h"
using namespace std;

class Flower {
  bool isOpen;
public:
  Flower() : isOpen(false), 
    openNotifier(this), closeNotifier(this) {}
  void open() { // Opens its petals
    isOpen = true;
    openNotifier.notifyObservers();
    closeNotifier.open();
  }
  void close() { // Closes its petals
    isOpen = false;
    closeNotifier.notifyObservers();
    openNotifier.close();
  }
  // Using the "inner class" idiom:
  class OpenNotifier;
  friend class Flower::OpenNotifier;
  class OpenNotifier : public Observable {
    Flower* parent;
    bool alreadyOpen;
  public:
    OpenNotifier(Flower* f) : parent(f), 
      alreadyOpen(false) {}
    void notifyObservers(Argument* arg=0) {
      if(parent->isOpen && !alreadyOpen) {
        setChanged();
        Observable::notifyObservers();
        alreadyOpen = true;
      }
    }
    void close() { alreadyOpen = false; }
  } openNotifier;
  class CloseNotifier;
  friend class Flower::CloseNotifier;
  class CloseNotifier : public Observable {
    Flower* parent;
    bool alreadyClosed;
  public:
    CloseNotifier(Flower* f) : parent(f), 
      alreadyClosed(false) {}
    void notifyObservers(Argument* arg=0) {
      if(!parent->isOpen && !alreadyClosed) {
        setChanged();
        Observable::notifyObservers();
        alreadyClosed = true;
      }
    }
    void open() { alreadyClosed = false; }
  } closeNotifier;
};

class Bee {
  string name;
  // An "inner class" for observing openings:
  class OpenObserver;
  friend class Bee::OpenObserver;
  class OpenObserver : public Observer {
    Bee* parent;
  public:
    OpenObserver(Bee* b) : parent(b) {}
    void update(Observable*, Argument *) {
      cout << "Bee " << parent->name 
        << "'s breakfast time!\n";
    }
  } openObsrv;
  // Another "inner class" for closings:
  class CloseObserver;
  friend class Bee::CloseObserver;
  class CloseObserver : public Observer {
    Bee* parent;
  public:
    CloseObserver(Bee* b) : parent(b) {}
    void update(Observable*, Argument *) {
      cout << "Bee " << parent->name 
        << "'s bed time!\n";
    }
  } closeObsrv;
public:
  Bee(string nm) : name(nm), 
    openObsrv(this), closeObsrv(this) {}
  Observer& openObserver() { return openObsrv; }
  Observer& closeObserver() { return closeObsrv;}
};

class Hummingbird {
  string name;
  class OpenObserver;
  friend class Hummingbird::OpenObserver;
  class OpenObserver : public Observer {
    Hummingbird* parent;
  public:
    OpenObserver(Hummingbird* h) : parent(h) {}
    void update(Observable*, Argument *) {
      cout << "Hummingbird " << parent->name 
        << "'s breakfast time!\n";
    }
  } openObsrv;
  class CloseObserver;
  friend class Hummingbird::CloseObserver;
  class CloseObserver : public Observer {
    Hummingbird* parent;
  public:
    CloseObserver(Hummingbird* h) : parent(h) {}
    void update(Observable*, Argument *) {
      cout << "Hummingbird " << parent->name 
        << "'s bed time!\n";
    }
  } closeObsrv;
public:
  Hummingbird(string nm) : name(nm), 
    openObsrv(this), closeObsrv(this) {}
  Observer& openObserver() { return openObsrv; }
  Observer& closeObserver() { return closeObsrv;}
};

int main() {
  Flower f;
  Bee ba("A"), bb("B");
  Hummingbird ha("A"), hb("B");
  f.openNotifier.addObserver(ha.openObserver());
  f.openNotifier.addObserver(hb.openObserver());
  f.openNotifier.addObserver(ba.openObserver());
  f.openNotifier.addObserver(bb.openObserver());
  f.closeNotifier.addObserver(ha.closeObserver());
  f.closeNotifier.addObserver(hb.closeObserver());
  f.closeNotifier.addObserver(ba.closeObserver());
  f.closeNotifier.addObserver(bb.closeObserver());
  // Hummingbird B decides to sleep in:
  f.openNotifier.deleteObserver(hb.openObserver());
  // Something changes that interests observers:
  f.open();
  f.open(); // It's already open, no change.
  // Bee A doesn't want to go to bed:
  f.closeNotifier.deleteObserver(
    ba.closeObserver());
  f.close();
  f.close(); // It's already closed; no change
  f.openNotifier.deleteObservers();
  f.open();
  f.close();
} ///:~ 
Theevents of interest are that a Flower can open or close. Because of the use of the inner class idiom, both theseevents can be separately-observable phenomena. OpenNotifier and CloseNotifier both inherit Observable ,so they have access to setChanged( ) and can be handed to anything that needs an Observable .You’ll notice that, contrary to InnerClassIdiom.cpp ,the Observable descendants are public .This is because some of their member functions must be available to the clientprogrammer. There’s nothing that says that an inner class must be private ;in InnerClassIdiom.cpp I was simply following the design guideline “make things as private aspossible.” You could make the classes private and expose the appropriate methods by proxy in Flower ,but it wouldn’t gain much.
Theinner class idiom also comes in handy to define more than one kind of Observer ,in Bee and Hummingbird ,since both those classes may want to independently observe Flower openings and closings. Notice how the inner class idiom provides something thathas most of the benefits of inheritance (the ability to access the private datain the outer class, for example) without the same restrictions.
In main( ) ,you can see one of the prime benefits of the observer pattern: the ability tochange behavior at run-time by dynamically registering and un-registering Observer swith Observable s.
Ifyou study the code above you’ll see that OpenNotifier and CloseNotifier use the basic Observable interface. This means that you could inherit other completely different Observer classes; the only connection the Observer shave with Flower sis the Observer interface.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值