Выбрать главу

The Outer constructor contains some private data (name), and it wants to provide both a Poingable interface and a Bingable interface so 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 the simplest syntax for the idiom; you’ll see a real use shortly. To provide a Poingable object without deriving Outer from Poingable, the inner class idiom is used. First, the declaration class Inner 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 class has 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 which implements 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.

Notice that both inner class definitions are private, and in fact the client code doesn’t have any access to details of the implementation, since the two access functions operator Poingable&( ) and operator Bingable&( ) only return a reference to the upcast interface, not to the object that implements it. In fact, since the two inner classes are private, the client code cannot even downcast to the implementation classes, thus providing complete isolation between interface and implementation.

Just to push a point, we’ve taken the extra liberty here of defining the automatic type conversion operators operator Poingable&( ) and operator Bingable&( ). In main( ), you can see that these actually allow a syntax that looks like Outer multiply inherits from Poingable and Bingable. The difference is that the casts in this case are one way. You can get the effect 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 typical approach: you provide access to the inner class objects using ordinary member functions, not automatic type conversion operations.

The observer example

Armed with the Observer and Observable header files and the inner class idiom, we can look at an example of the Observer pattern:

//: C10:ObservedFlower.cpp

// Demonstration of "observer" pattern

#include <algorithm>

#include <iostream>

#include <string>

#include <vector>

#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;