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


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


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


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


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


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 RegisterObserver(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 RegisterObserver(ClockObserver observer)   {     itsObservers.Add(observer);   } }

Figure 32-6. Handling multiple TimeSink objects


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


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


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#


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


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.

[2] "Push model" observers push data from the subject to the observer by passing it in the Notify and Update methods. "Pull model" observers pass nothing in the Notify and Update methods and depend on the observing object to query the observed object on receiving an update. See [GOF95].

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


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();   } }




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net