32. Observer: Evolving into a Pattern

image

© Jennifer M. Kohnke

I prefer to describe my profession as that of a “Contemporary Anthropological Interactive OBSERVER” because it has just the right amount of flair. Besides, “stalker” is such an ugly word.

—Anonymous

This chapter serves a special purpose. In it, I describe the OBSERVER1 pattern, but that is a minor objective. The primary objective of this chapter is to demonstrate how your design and code can evolve to use a pattern.

The preceding chapters made use of many patterns. Often, they were presented without showing how the code evolved to use the pattern. This might give you the idea that patterns are simply something you insert into your code and designs in completed form. This is not what I advise. Rather, I prefer to evolve the code I am working on in the direction of a pattern. I may get to the pattern, or I may not. It depends on whether the issues get resolved. It is not uncommon for me to start with a pattern in mind and wind up at a very different place.

This chapter sets up a simple problem and then shows how the design and code evolve to solve that problem. The goal of the evolution is the OBSERVER pattern. At each stage of the evolution, I describe the issues I’m trying to resolve and then show the steps that resolve them. With luck, we’ll wind up with an OBSERVER.

The Digital Clock

We have a clock object that catches millisecond interrupts, known as tics, from the operating system and turns them into the time of day. This object knows how to calculate seconds from milliseconds, minutes from seconds, hours from minutes, days from hours, and so on. It knows how many days are in a month and how many months are in a year. It knows all about leap years, when to have them, and when not. It knows about time. See Figure 32-1.

Figure 32-1. Clock object

image

We’d like to create a digital clock that sits on our desktop and continuously displays the time of day. What is the simplest way to accomplish this? We could write this:

public void DisplayTime()
{
  while (true)
  {
    int sec = clock.Seconds;
    int min = clock.Minutes;
    int hour = clock.Hours;
    ShowTime(hour, min, sec);
  }
}

Clearly, this is suboptimal. It consumes all available CPU cycles to repeatedly display the time. Most of those displays will be wasted because the time will not have changed. It may be that this solution would be adequate in a digital watch or a digital wall clock, since in those systems, conserving CPU cycles is not very important. However, we don’t want this CPU hog running on our desktop.

Thus, the way in which the time moves from the clock to the display is going to be nontrivial. What mechanism should I use? Before first, I need to ask another question. How do I test that the mechanism is doing what I want?

The fundamental problem I am exploring is how to get data from the Clock to the DigitalClock. I’m going to assume that the Clock object and the DigitalClock object both exist. My interest is in how to connect them. I can test that connection simply by making sure that the data I get from the Clock is the same data I send to the DigitalClock.

A simple way to do that is to create one interface that pretends to be the Clock and another interface that pretends to be the DigitalClock. Then I can write special test objects that implement those interfaces and verify that the connection between them works as expected. See Figure 32-2.

Figure 32-2. Testing the DigitalClock

image

The ClockDriverTest object will connect the ClockDriver to the two mock objects through the TimeSource and TimeSink interfaces and will then check each of the mock objects to ensure that the ClockDriver managed to move the time from the source to the sink. If necessary, the ClockDriverTest will also ensure that efficiency is being conserved.

I think it’s interesting that we have added interfaces to the design simply as a result of considering how to test it. In order to test a module, you have to be able to isolate it from the other modules in the system, just as we have isolated the ClockDriver from the Clock and DigitalClock. Considering tests first helps us to minimize the coupling in our designs.

OK, how does the ClockDriver work? Clearly, in order to be efficient, the ClockDriver must detect when the time in the TimeSource object has changed. Then, and only then, should it move the time to the TimeSink object. How can the ClockDriver know when the time has changed? It could poll the TimeSource, but that simply recreates the CPU hog problem.

The simplest way for the ClockDriver to know when the time has changed is for the Clock object to tell it. We could pass the ClockDriver to the Clock through the TimeSource interface, and then, when the time changes, the Clock can update the ClockDriver. The ClockDriver will, in turn, set the time on the ClockSink. See Figure 32-3.

Figure 32-3. Getting the TimeSource to update the ClockDriver

image

Note the dependency from the TimeSource to the ClockDriver. It is there because the argument to the SetDriver method is a ClockDriver. I’m not very happy with this, since it implies that TimeSource objects must use ClockDriver objects in every case. However, I’ll defer doing anything about this until I get this working.

Listing 32-1 shows the test case for the ClockDriver. Note that the test case creates a ClockDriver, binds a MockTimeSource and a MockTimeSink to it, and then sets the time in the source and expects the time to magically arrive at the sink. The rest of the code is shown in Listings 32-2 through 32-6.


Listing 32-1. ClockDriverTest.cs

using NUnit.Framework;

[TestFixture]
public class ClockDriverTest
{
  [Test]
  public void TestTimeChange()
  {
    MockTimeSource source = new MockTimeSource();
    MockTimeSink sink = new MockTimeSink();
    ClockDriver driver = new ClockDriver(source,sink);
    source.SetTime(3,4,5);
    Assert.AreEqual(3, sink.GetHours());
    Assert.AreEqual(4, sink.GetMinutes());
    Assert.AreEqual(5, sink.GetSeconds());

    source.SetTime(7,8,9);
    Assert.AreEqual(7, sink.GetHours());
    Assert.AreEqual(8, sink.GetMinutes());
    Assert.AreEqual(9, sink.GetSeconds());
  }
}



Listing 32-2. TimeSource.cs

public interface TimeSource
{
  void SetDriver(ClockDriver driver);
}



Listing 32-3. TimeSink.cs

public interface TimeSink
{
  void SetTime(int hours, int minutes, int seconds);
}



Listing 32-4. ClockDriver.cs

public class ClockDriver
{
  private readonly TimeSink sink;

  public ClockDriver(TimeSource source, TimeSink sink)

  {
    source.SetDriver(this);
    this.sink = sink;
  }
  public void Update(int hours, int minutes, int seconds)
  {
    sink.SetTime(hours, minutes, seconds);
  }
}



Listing 32-5. MockTimeSource.cs

public class MockTimeSource : TimeSource
{
  private ClockDriver itsDriver;

  public void SetTime(int hours, int minutes, int seconds)
  {
    itsDriver.Update(hours, minutes, seconds);
  }

  public void SetDriver(ClockDriver driver)
  {
    itsDriver = driver;
  }
}



Listing 32-6. MockTimeSink.cs

public class MockTimeSink : TimeSink
{
  private int itsHours;
  private int itsMinutes;
  private int itsSeconds;

  public int GetHours()
  {
    return itsHours;
  }

  public int GetMinutes()
  {
    return itsMinutes;
  }

  public int GetSeconds()
  {
    return itsSeconds;
  }

  public void SetTime(int hours, int minutes, int seconds)
  {
    itsHours = hours;
    itsMinutes = minutes;
    itsSeconds = seconds;
  }
}


Now that it works, I can think about cleaning it up. I don’t like the dependency from TimeSource to ClockDriver. I don’t like it because I want the TimeSource interface to be usable by anybody, not just ClockDriver objects. As it stands, only ClockDriver instances can use a TimeSource. We can fix this by creating an interface that TimeSource can use and that ClockDriver can implement (see Figure 32-4). We’ll call this interface ClockObserver. See Listings 32-7 through 32-10. The code in boldface has changed.

Figure 32-4. Breaking the dependency of TimeSource on ClockDriver

image


Listing 32-7. ClockObserver.cs

public interface ClockObserver
{
  void Update(int hours, int minutes, int secs);
}



Listing 32-8. ClockDriver.cs

public class ClockDriver : ClockObserver
{
  private readonly TimeSink sink;

  public ClockDriver(TimeSource source, TimeSink sink)
  {
    source.SetObserver(this);
    this.sink = sink;
  }

  public void Update(int hours, int minutes, int seconds)
  {
    sink.SetTime(hours, minutes, seconds);
  }
}



Listing 32-9. TimeSource.cs

public interface TimeSource
{
  void SetObserver(ClockObserver observer);
}



Listing 32-10. MockTimeSource.cs

public class MockTimeSource : TimeSource
{
  private ClockObserver itsObserver;

  public void SetTime(int hours, int minutes, int seconds)
  {
    itsObserver.Update(hours, minutes, seconds);
  }

  public void SetObserver(ClockObserver observer)
  {
    itsObserver = observer;
  }
}


This is better. Now anybody can make use of TimeSource by implementing ClockObserver and calling SetObserver, passing themselves in as the argument.

I’d like to be able to have more than one TimeSink getting the time. One might implement a digital clock. Another might be used to supply the time to a reminder service. Still another might start my nightly backup. In short, I’d like a single TimeSource to be able to supply the time to multiple TimeSink objects.

How do I do this? Right now, I create a ClockDriver with a single TimeSource and a single TimeSink. How should I specify multiple TimeSink instances? I could change the constructor of the ClockDriver to take only TimeSource and then add a method named addTimeSink that allows you to add TimeSink instances whenever you want.

The thing I don’t like about this is that I now have two indirections. I have to tell the TimeSource who the ClockObserver is by calling SetObserver. Then I also have to tell the ClockDriver who the TimeSink instances are. Is this double indirection really necessary?

Looking at ClockObserver and TimeSink, I see that they both have the SetTime method. It looks as though TimeSink could implement ClockObserver. If I did this, my test program could create a MockTimeSink and call SetObserver on the TimeSource. I could get rid of the ClockDriver and TimeSink altogether! Listing 32-11 shows the changes to ClockDriverTest.


Listing 32-11. ClockDriverTest.cs

using NUnit.Framework;

[TestFixture]
public class ClockDirverTest
{
  [Test]
  public void TestTimeChange()
  {
    MockTimeSource source = new MockTimeSource();
    MockTimeSink sink = new MockTimeSink();
    source.SetObserver(sink);

    source.SetTime(3,4,5);
    Assert.AreEqual(3, sink.GetHours());
    Assert.AreEqual(4, sink.GetMinutes());
    Assert.AreEqual(5, sink.GetSeconds());

    source.SetTime(7,8,9);
    Assert.AreEqual(7, sink.GetHours());
    Assert.AreEqual(8, sink.GetMinutes());
    Assert.AreEqual(9, sink.GetSeconds());
  }
}


This means that MockTimeSink should implement ClockObserver rather than TimeSink. See Listing 32-12. These changes work fine. Why did we think we needed a ClockDriver in the first place? Figure 32-5 shows the UML. Clearly, this is much simpler.


Listing 32-12. MockTimeSink.cs

public class MockTimeSink : ClockObserver
{
  private int itsHours;
  private int itsMinutes;
  private int itsSeconds;

  public int GetHours()
  {
    return itsHours;
  }

  public int GetMinutes()
  {
    return itsMinutes;
  }

  public int GetSeconds()
  {
    return itsSeconds;
  }

  public void Update(int hours, int minutes, int secs)
  {
    itsHours = hours;
    itsMinutes = minutes;
    itsSeconds = secs;
  }
}


Figure 32-5. Removing ClockDriver and TimeSink

image

OK, now we can handle multiple TimeSink objects by changing the setObserver function to registerObserver and making sure that all the registered ClockObserver instances are held in a list and updated appropriately. This requires another change to the test program. Listing 32-13 shows the changes. I also did a little refactoring of the test program to make it smaller and easier to read.


Listing 32-13. ClockDriverTest.cs

using NUnit.Framework;

[TestFixture]
public class ClockDriverTest
{
  private MockTimeSource source;
  private MockTimeSink sink;

  [SetUp]
  public void SetUp()
  {
    source = new MockTimeSource();
    sink = new  MockTimeSink();
    source.RegisterObserver(sink);
  }

  private void AssertSinkEquals(
    MockTimeSink sink, int hours, int mins, int secs)
  {
    Assert.AreEqual(hours, sink.GetHours());
    Assert.AreEqual(mins, sink.GetMinutes());
    Assert.AreEqual(secs, sink.GetSeconds());
  }

  [Test]
  public void TestTimeChange()
  {
    source.SetTime(3,4,5);
    AssertSinkEquals(sink, 3,4,5);

    source.SetTime(7,8,9);
    AssertSinkEquals(sink, 7,8,9);
  }

  [Test]
  public void TestMultipleSinks()
  {
    MockTimeSink sink2 = new MockTimeSink();
    source.RegisterObserver(sink2);

    source.SetTime(12,13,14);
    AssertSinkEquals(sink, 12,13,14);
    AssertSinkEquals(sink2, 12,13,14);
  }
}


The change needed to make this work is pretty simple. We change MockTimeSource to hold all registered observers in an ArrayList. Then, when the time changes, we iterate through that list and call Update on all the registered ClockObservers. Listings 32-14 and 32-15 show the changes. Figure 32-6 shows the corresponding UML.


Listing 32-14. TimeSource.cs

public interface TimeSource
{
  void Register Observer(ClockObserver observer);
}



Listing 32-15. MockTimeSource.cs

using System.Collections;

public class MockTimeSource : TimeSource
{
  private ArrayList itsObservers = new ArrayList();

  public void SetTime(int hours, int mins, int secs)
  {
    foreach(ClockObserver observer in itsObservers)
      observer.Update(hours, mins, secs);
  }

  public void Register Observer(ClockObserver observer)
  {
    itsObservers.Add(observer);
  }
}


Figure 32-6. Handling multiple TimeSink objects

image

This is pretty nice, but I don’t like the fact that the MockTimeSource has to deal with the registration and update. It implies that the Clock, and every other derivative of Time-Source, will have to duplicate that registration and update code. I don’t think Clock should have to deal with registration and update. I also don’t like the idea of duplicate code. So I’d like to move all that stuff into the TimeSource. Of course, this will mean that TimeSource will have to change from an interface to a class. It also means that MockTimeSource will shrink to near nothing. Listings 32-16 and 32-17, and Figure 32-7 show the changes.

Figure 32-7. Moving registration and update into TimeSource

image


Listing 32-16. TimeSource.cs

using System.Collections;

public abstract class TimeSource
{
  private ArrayList itsObservers = new ArrayList();

  protected void Notify(int hours, int mins, int secs)
  {
    foreach(ClockObserver observer in itsObservers)
      observer.Update(hours, mins, secs);
  }

  public void RegisterObserver(ClockObserver observer)
  {
    itsObservers.Add(observer);
  }
}



Listing 32-17. MockTimeSource.cs

public class MockTimeSource : TimeSource
{
  public void SetTime(int hours, int mins, int secs)
  {
    Notify(hours, mins, secs);
  }
}


This is pretty cool. Now, anybody can derive from TimeSource. All they have to do to get the observers updated is to call Notify. But there is still something I don’t like about it. MockTimeSource inherits directly from TimeSource. This means that Clock must also derive from TimeSource. Why should Clock have to depend upon registration and update? Clock is simply a class that knows about time. Making it depend upon TimeSource seems necessary and undesirable.

I know how I’d solve this in C++. I’d create a subclass, called ObservableClock, of both TimeSource and Clock. I’d override Tic and SetTime in ObservableClock to call Tic or SetTime in Clock and then call Notify in TimeSource. See Listings 32-8 and 32-18.


Listing 32-18. ObservableClock.cc (C++)

class ObservableClock : public Clock, public TimeSource
{
  public:
    virtual void tic()
    {
      Clock::tic();
      TimeSource::notify(getHours(),
                          getMinutes(),
                          getSeconds());
    }

    virtual void aetTime(int hours, int minutes, int seconds)
    {
      Clock::setTime(hours, minutes, seconds);
      TimeSource::notify(hours, minutes, seconds);
    }
};


Unfortunately, we don’t have this option in C#, because the language can’t deal with multiple inheritance of classes. So, in C#, we either have to leave things as they are or use a delegation hack. The delegation hack is shown in Listings 32-19 through 32-21 and Figure 32-9.

Note that the MockTimeSource class implements TimeSource and contains a reference to an instance of TimeSourceImplementation. Note also that all calls to the RegisterObserver method of MockTimeSource are delegated to that TimeSourceImplementation object. So, too, MockTimeSource.SetTime invokes Notify on the TimeSourceImplementation instance.

Figure 32-8. Using multiple inheritance in C++ to separate Clock from TimeSource

image


Listing 32-19. TomeSource.cs

public interface TimeSource
{
  void RegisterObserver(ClockObserver observer);
}



Listing 32-20. TimeSourceImplementation.cs

using System.Collections;

public class TimeSourceImplementation : TimeSource
{
  private ArrayList itsObservers = new ArrayList();

  public void Notify(int hours, int mins, int secs)
  {
    foreach(ClockObserver observer in itsObservers)
      observer.Update(hours, mins, secs);
  }

  public void RegisterObserver(ClockObserver observer)
  {
    itsObservers.Add(observer);
  }
}



Listing 32-21. MockTimeSource.cs

public class MockTimeSource : TimeSource
{
  TimeSourceImplementation timeSourceImpl =
    new TimeSourceImplementation();

  public void SetTime(int hours, int mins, int secs)
  {
    timeSourceImpl.Notify(hours, mins, secs);
  }

  public void RegisterObserver(ClockObserver observer)
  {
    timeSourceImpl.RegisterObserver(observer);
  }
}


Figure 32-9. Observer delegation hack in C#

image

This is ugly but has the advantage that MockTimeSource does not extend a class. This means that if we were to create ObservableClock, it could extend Clock, implement TimeSource, and delegate to TimeSourceImplementation (see Figure 32-10). This solves the problem of Clock depending on the registration and update stuff but does so at a nontrivial price.

Figure 32-10. The delegation hack for ObservableClock

image

So, let’s go back to the way things were in Figure 32-7, before we went down this rathole. We’ll simply live with the fact that Clock has to depend upon all the registration and update stuff.

TimeSource is a stupid name for what the class does. It started out good, back when we had a ClockDriver. But things have changed an awful lot since then. We should change the name to something that suggests registration and update. The OBSERVER pattern calls this class Subject. Ours seems to be specific to time, so we could call it TimeSubject, but that’s not a very intutitive name. We could use the old moniker Observable, but that doesn’t ring my chimes, either: TimeObservable? No.

Perhaps it is the specificity of the “push model”2 observer that is the problem. If we change to a “pull model,” we could make the class generic. Then we could change the name of TimeSource to Subject, and everybody familiar with the OBSERVER pattern would know what it meant.

This is not a bad option. Rather than pass the time in the Notify and Update methods, we can have the TimeSink ask the MockTimeSource for the time. We don’t want the MockTimeSink to know about the MockTimeSource, so we’ll create an interface that the MockTimeSink can use to get the time. The MockTimeSource and the Clock will implement this interface. We’ll call this interface TimeSource. The final state of the code and UML are in Figure 32-11 and Listings 32-22 through 32-27.

Figure 32-11. Final version of the Observer applied to MockTimeSource and MockTimeSink

image


Listing 32-22. ObserverTest.cs

using NUnit.Framework;

[TestFixture]
public class ObserverTest
{
  private MockTimeSource source;
  private MockTimeSink sink;

  [SetUp]
  public void SetUp()
  {
    source = new MockTimeSource();
    sink = new MockTimeSink();
    source.RegisterObserver(sink);
  }

  private void AssertSinkEquals(
    MockTimeSink sink, int hours, int mins, int secs)
  {
    Assert.AreEqual(hours, sink.GetHours());
    Assert.AreEqual(mins, sink.GetMinutes());
    Assert.AreEqual(secs, sink.GetSeconds());
  }

  [Test]
  public void TestTimeChange()
  {
    source.SetTime(3,4,5);
    AssertSinkEquals(sink, 3,4,5);

    source.SetTime(7,8,9);
    AssertSinkEquals(sink, 7,8,9);
  }

  [Test]
  public void TestMultipleSinks()
  {
    MockTimeSink sink2 = new MockTimeSink();
    source.RegisterObserver(sink2);

    source.SetTime(12,13,14);
    AssertSinkEquals(sink, 12,13,14);
    AssertSinkEquals(sink2, 12,13,14);
  }
}



Listing 32-23. Observer.cs

public interface Observer
{
  void Update();
}



Listing 32-24. Subject.cs

using System.Collections;

public class Subject
{
  private ArrayList itsObservers = new ArrayList();

  public void NotifyObservers()
  {
    foreach(Observer observer in itsObservers)
      observer.Update();
  }

  public void RegisterObserver(Observer observer)
  {
    itsObservers.Add(observer);
  }
}



Listing 32-25. TimeSource.cs

public interface TimeSource
{
  int GetHours();
  int GetMinutes();
  int GetSeconds();
}



Listing 32-26. MockTimeSource.cs

public class MockTimeSource : Subject, TimeSource
{
  private int itsHours;
  private int itsMinutes;
  private int itsSeconds;

  public void SetTime(int hours, int mins, int secs)
  {
    itsHours = hours;
    itsMinutes = mins;
    itsSeconds = secs;
    NotifyObservers();
  }

  public int GetHours()
  {
    return itsHours;
  }

  public int GetMinutes()

  {
    return itsMinutes;
  }

  public int GetSeconds()
  {
    return itsSeconds;
  }
}



Listing 32-27. MockTimeSink.cs

public class MockTimeSink : Observer
{
  private int itsHours;
  private int itsMinutes;
  private int itsSeconds;
  private TimeSource itsSource;

  public MockTimeSink(TimeSource source)
  {
    itsSource = source;
  }

  public int GetHours()
  {
    return itsHours;
  }

  public int GetMinutes()
  {
    return itsMinutes;
  }

  public int GetSeconds()
  {
    return itsSeconds;
  }

  public void Update()
  {
    itsHours = itsSource.GetHours();
    itsMinutes = itsSource.GetMinutes();
    itsSeconds = itsSource.GetSeconds();
  }
}


The OBSERVER Pattern

OK, so now that we’ve been through the example and evolved our code to the OBSERVER pattern, it might be interesting to study what it is. The canonical form of OBSERVER is shown in Figure 32-12. In this example, Clock is being observed by DigitalClock, which registers with the Subject interface of Clock. Whenever the time changes for any reason, Clock calls the Notify method of Subject. The Notify method of Subject invokes the Update method of each registered Observer. Thus, DigitalClock will receive an Update message whenever the time changes, using that opportunity to ask Clock for the time and then display it.

Figure 32-12. Canonical pull-model Observer

image

OBSERVER is one of those patterns that, once you understand it, you see uses for it everywhere. The indirection is very cool. You can register observers with all kinds of objects rather than writing those objects to explicitly call you. Although this indirection is a useful way to manage dependencies, it can easily be taken to extremes. Overuse of OBSERVER tends to make systems difficult to understand and trace.

Models

The OBSERVER pattern has two primary models. Figure 32-12 shows the pull-model OBSERVER. This model gets its name from the fact that the DigitalClock must pull the time information from the Clock object after receiving the Update message.

The advantage of the pull model is its simplicity of implementation and the fact that the Subject and Observer classes can be standard reusable elements in a library. However, imagine that you are observing an employee record with a thousand fields and that you have just received an Update message. Which of the thousand fields changed?

When Update is called on the ClockObserver, the response is obvious. The ClockObserver needs to pull the time from the Clock and display it. But when Update is called on the EmployeeObserver, the response is not so obvious. We don’t know what happened. We don’t know what to do. Perhaps the employee’s name changed, or maybe the employee’s salary changed. Maybe the employee got a new boss. Or maybe the employee’s bank account changed. We need help.

This help can be given in the push-model form of the OBSERVER pattern. The structure of the push model is shown in Figure 32-13. Note that the Notify and Update methods both take an argument. The argument is a hint, passed from Employee to SalaryObserver through the Notify and Update methods. That hint tells SalaryObserver what kind of change the Employee record experienced.

Figure 32-13. Push-model Observer

image

The EmployeeObserverHint argument of Notify and Update might be an enumeration of some kind, a string, or a more complex data structure containing the old and new values of some field. Whatever it is, its value is being pushed toward the observer.

Choosing between the two OBSERVER models is simply a matter of the complexity of the observed object. If the observed object is complex and the observer needs a hint, the push model is appropriate. If the observed object is simple, a pull model will do fine.

Management of OOD Principles

The principle that most drives the OBSERVER pattern is the Open/Closed Principle (OCP). The motivation for using the pattern is so that you can add new observing objects without changing the observed object. Thus, the observed object stays closed.

From Figure 32-12, it should be clear that Clock is substitutable for Subject and that DigitalClock is substitutable for Observer. Thus, the Liskov Substitution Principle (LSP) is applied.

Observer is an abstract class, and the concrete DigitialClock depends on it. The concrete methods of Subject also depend on it. Thus, the Dependency-Inversion Principle (DIP) is applied in this case. You might think that since Subject has no abstract methods, the dependency between Clock and Subject violates DIP. However, Subject is a class that ought never to be instantiated. It makes sense only in the context of a derived class. Thus, Subject is logically abstract, even though it has no abstract methods. We can enforce the abstractness of Subject by giving it a pure virtual destructor in C++ or by making its constructors protected.

There are hints of the Interface Segregation Principle (ISP) in Figure 32-11. The Subject and TimeSource classes segregate the clients of the MockTimeSource, providing specialized interfaces for each of those clients.

Conclusion

So, we made it. We started with a design problem and, through reasonable evolution, brought it pretty close to the canonical OBSERVER pattern. You might complain that since I knew that I wanted to arrive at the OBSERVER, I simply arranged it so that I would. I won’t deny it. But that’s not really the issue.

If you are familiar with design patterns, an appropriate pattern will very likely pop into your mind when you’re faced with a design problem. The question, then, is whether to implement that pattern directly or instead to evolve it into place through a series of small steps. This chapter showed what the second option is like. Rather than simply leaping to the conclusion that the Observer pattern was the best choice for the problem at hand, I slowly maneuvered the code in that direction.

At any point during that evolution, I could have found that my problem was solved and stopped evolving. Or, I might have found that I could solve the problem by changing course and going in a different direction.

I drew in this chapter some of the diagrams for your benefit. I thought it would be easier for you to follow what I was doing by showing you an overview in a diagram. Had I not been trying to expose and expound, I would not have created them. However, a few of the diagrams were created for my benefit. There were times when I simply needed to stare at the structure that I had created so I could see where to go next.

Had I not been writing a book, I would have drawn these diagrams by hand on a scrap of paper or a whiteboard. I would not have taken the time to use a drawing tool. There are no circumstances that I know of in which using a drawing tool is faster than using a napkin.

Having used the diagrams to help me evolve the code, I would not have kept the diagrams. In every case, the ones I drew for myself were intermediate steps.

Is there value in keeping diagrams at this level of detail? Clearly, if you are trying to expose your reasoning, as I am doing in this book, they come in pretty handy. But usually, we are not trying to document the evolutionary path of a few hours of coding. Usually, these diagrams are transient and are better thrown away. At this level of detail, the code is generally good enough to act as its own documentation. At higher levels, this is not always true.

Bibliography

[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.

[PLOPD3] Robert C. Martin, Dirk Riehle, and Frank Buschmann, eds. Pattern Languages of Program Design 3, Addison-Wesley, 1998.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.47.253