© 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.
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.
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.
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.
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());
}
}
public interface TimeSource
{
void SetDriver(ClockDriver driver);
}
public interface TimeSink
{
void SetTime(int hours, int minutes, int seconds);
}
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;
}
}
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.
Listing 32-7. ClockObserver.cs
public interface ClockObserver
{
void Update(int hours, int minutes, int secs);
}
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);
}
}
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;
}
}
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.
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);
}
}
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.
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.
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);
}
}
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.
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.
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);
}
}
public interface Observer
{
void Update();
}
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);
}
}
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();
}
}
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.
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.
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.
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.
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.
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.
[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.
3.133.12.172