OOverkill


This example has certain pedagogical advantages. It is small and easy to understand and shows how the principles of OOD can be used to manage dependencies and separate concerns. On the other hand, its very smallness means that the benefits of that separation probably do not outweigh the costs.

If we were to write the Mark IV coffee maker as an FSM, we'd find that it had 7 states and 18 transitions.[5] We could encode this into 18 lines of SMC code. A simple main loop that polls the sensors would be another ten lines or so, and the action functions that the FSM would invoke would be another couple of dozen. In short, we could write the whole program in less than a page of code.

[5] [Martin1995], p. 65

If we don't count the tests, the OO solution of the coffee maker is five pages of code. There is no way that we can justify this disparity. In larger applications, the benefits of dependency management and the separation of concerns clearly outweigh the costs of OOD. In this example, however, the reverse is more likely to be true.

Listing 20-14. UserInterface.cs

using System; namespace CoffeeMaker {   public abstract class UserInterface   {     private HotWaterSource hws;     private ContainmentVessel cv;     protected bool isComplete;     public UserInterface()     {       isComplete = true;     }     public void Init(HotWaterSource hws, ContainmentVessel cv)     {       this.hws = hws;       this.cv = cv;     }     public void Complete()     {       isComplete = true;       CompleteCycle();     }     protected void StartBrewing()     {       if (hws.IsReady() && cv.IsReady())       {         isComplete = false;         hws.Start();         cv.Start();       }     }     public abstract void Done();     public abstract void CompleteCycle();   } }

Listing 20-15. M4UserInterface.cs

using CoffeeMaker; namespace M4CoffeeMaker {   public class M4UserInterface : UserInterface                                , Pollable   {     private CoffeeMakerAPI api;     public M4UserInterface(CoffeeMakerAPI api)     {       this.api = api;     }     public void Poll()     {       BrewButtonStatus buttonStatus = api.GetBrewButtonStatus();       if (buttonStatus == BrewButtonStatus.PUSHED)       {         StartBrewing();       }     }     public override void Done()     {       api.SetIndicatorState(IndicatorState.ON);     }     public override void CompleteCycle()     {       api.SetIndicatorState(IndicatorState.OFF);     }   } }

Listing 20-16. HotWaterSource.cs

namespace CoffeeMaker {   public abstract class HotWaterSource   {     private UserInterface ui;     private ContainmentVessel cv;     protected bool isBrewing;     public HotWaterSource()     {       isBrewing = false;     }     public void Init(UserInterface ui, ContainmentVessel cv)     {       this.ui = ui;       this.cv = cv;     }     public void Start()     {       isBrewing = true;       StartBrewing();     }     public void Done()     {       isBrewing = false;     }     protected void DeclareDone()     {       ui.Done();       cv.Done();       isBrewing = false;     }     public abstract bool IsReady();     public abstract void StartBrewing();     public abstract void Pause();     public abstract void Resume();   } }

Listing 20-17. M4HotWaterSource.cs

using System; using CoffeeMaker; namespace M4CoffeeMaker {   public class M4HotWaterSource : HotWaterSource                                 , Pollable   {     private CoffeeMakerAPI api;     public M4HotWaterSource(CoffeeMakerAPI api)     {       this.api = api;     }     public override bool IsReady()     {       BoilerStatus boilerStatus = api.GetBoilerStatus();       return boilerStatus == BoilerStatus.NOT_EMPTY;     }     public override void StartBrewing()     {       api.SetReliefValveState(ReliefValveState.CLOSED);       api.SetBoilerState(BoilerState.ON);     }     public void Poll()     {       BoilerStatus boilerStatus = api.GetBoilerStatus();       if (isBrewing)       {         if (boilerStatus == BoilerStatus.EMPTY)         {           api.SetBoilerState(BoilerState.OFF);           api.SetReliefValveState(ReliefValveState.CLOSED);           DeclareDone();         }       }     }     public override void Pause()     {       api.SetBoilerState(BoilerState.OFF);       api.SetReliefValveState(ReliefValveState.OPEN);     }     public override void Resume()     {       api.SetBoilerState(BoilerState.ON);       api.SetReliefValveState(ReliefValveState.CLOSED);     }   } }

Listing 20-18. ContainmentVessel.cs

using System; namespace CoffeeMaker {   public abstract class ContainmentVessel   {     private UserInterface ui;     private HotWaterSource hws;     protected bool isBrewing;     protected bool isComplete;     public ContainmentVessel()     {       isBrewing = false;       isComplete = true;     }     public void Init(UserInterface ui, HotWaterSource hws)     {       this.ui = ui;       this.hws = hws;     }     public void Start()     {       isBrewing = true;       isComplete = false;     }     public void Done()     {       isBrewing = false;     }     protected void DeclareComplete()     {       isComplete = true;       ui.Complete();     }     protected void ContainerAvailable()     {       hws.Resume();     }     protected void ContainerUnavailable()     {       hws.Pause();     }     public abstract bool IsReady();   } }

Listing 20-19. M4ContainmentVessel.cs

using CoffeeMaker; namespace M4CoffeeMaker {   public class M4ContainmentVessel : ContainmentVessel                                    , Pollable   {     private CoffeeMakerAPI api;     private WarmerPlateStatus lastPotStatus;     public M4ContainmentVessel(CoffeeMakerAPI api)     {       this.api = api;       lastPotStatus = WarmerPlateStatus.POT_EMPTY;     }     public override bool IsReady()     {       WarmerPlateStatus plateStatus =                          api.GetWarmerPlateStatus();       return plateStatus == WarmerPlateStatus.POT_EMPTY;     }     public void Poll()     {       WarmerPlateStatus potStatus = api.GetWarmerPlateStatus();       if (potStatus != lastPotStatus)       {         if (isBrewing)         {           HandleBrewingEvent(potStatus);         }         else if (isComplete == false)         {           HandleIncompleteEvent(potStatus);         }         lastPotStatus = potStatus;       }     }     private void     HandleBrewingEvent(WarmerPlateStatus potStatus)     {       if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)       {         ContainerAvailable();         api.SetWarmerState(WarmerState.ON);       }       else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)       {         ContainerUnavailable();         api.SetWarmerState(WarmerState.OFF);       }       else       { // potStatus == POT_EMPTY         ContainerAvailable();         api.SetWarmerState(WarmerState.OFF);       }     }     private void     HandleIncompleteEvent(WarmerPlateStatus potStatus)     {       if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)       {         api.SetWarmerState(WarmerState.ON);       }       else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)       {         api.SetWarmerState(WarmerState.OFF);       }       else       { // potStatus == POT_EMPTY         api.SetWarmerState(WarmerState.OFF);         DeclareComplete();       }     }   } }

Listing 20-20. Pollable.cs

using System; namespace M4CoffeeMaker {   public interface Pollable   {     void Poll();   } }

Listing 20-21. CoffeeMaker.cs

using CoffeeMaker; namespace M4CoffeeMaker {   public class M4CoffeeMaker   {     public static void Main(string[] args)     {       CoffeeMakerAPI api = new M4CoffeeMakerAPI();       M4UserInterface ui = new M4UserInterface(api);       M4HotWaterSource hws = new M4HotWaterSource(api);       M4ContainmentVessel cv = new M4ContainmentVessel(api);       ui.Init(hws, cv);       hws.Init(ui, cv);       cv.Init(ui, hws);       while (true)       {         ui.Poll();         hws.Poll();         cv.Poll();       }     }   } }

Listing 20-22. TestCoffeeMaker.cs

using M4CoffeeMaker; using NUnit.Framework; namespace CoffeeMaker.Test {   internal class CoffeeMakerStub : CoffeeMakerAPI   {     public bool buttonPressed;     public bool lightOn;     public bool boilerOn;     public bool valveClosed;     public bool plateOn;     public bool boilerEmpty;     public bool potPresent;     public bool potNotEmpty;     public CoffeeMakerStub()     {       buttonPressed = false;       lightOn = false;       boilerOn = false;       valveClosed = true;       plateOn = false;       boilerEmpty = true;       potPresent = true;       potNotEmpty = false;     }     public WarmerPlateStatus GetWarmerPlateStatus()     {       if (!potPresent)         return WarmerPlateStatus.WARMER_EMPTY;       else if (potNotEmpty)         return WarmerPlateStatus.POT_NOT_EMPTY;       else         return WarmerPlateStatus.POT_EMPTY;     }     public BoilerStatus GetBoilerStatus()     {       return boilerEmpty ?              BoilerStatus.EMPTY : BoilerStatus.NOT_EMPTY;     }     public BrewButtonStatus GetBrewButtonStatus()     {       if (buttonPressed)       {         buttonPressed = false;         return BrewButtonStatus.PUSHED;       }       else       {         return BrewButtonStatus.NOT_PUSHED;       }     }     public void SetBoilerState(BoilerState boilerState)     {       boilerOn = boilerState == BoilerState.ON;     }     public void SetWarmerState(WarmerState warmerState)     {       plateOn = warmerState == WarmerState.ON;     }     public void     SetIndicatorState(IndicatorState indicatorState)     {       lightOn = indicatorState == IndicatorState.ON;     }     public void     SetReliefValveState(ReliefValveState reliefValveState)     {       valveClosed = reliefValveState == ReliefValveState.CLOSED;     }   }   [TestFixture]   public class TestCoffeeMaker   {     private M4UserInterface ui;     private M4HotWaterSource hws;     private M4ContainmentVessel cv;     private CoffeeMakerStub api;     [SetUp]     public void SetUp()     {       api = new CoffeeMakerStub();       ui = new M4UserInterface(api);       hws = new M4HotWaterSource(api);       cv = new M4ContainmentVessel(api);       ui.Init(hws, cv);       hws.Init(ui, cv);       cv.Init(ui, hws);     }     private void Poll()     {       ui.Poll();       hws.Poll();       cv.Poll();     }     [Test]     public void InitialConditions()     {       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void StartNoPot()     {       Poll();       api.buttonPressed = true;       api.potPresent = false;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void StartNoWater()     {       Poll();       api.buttonPressed = true;       api.boilerEmpty = true;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void GoodStart()     {       NormalStart();       Assert.IsTrue(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     private void NormalStart()     {       Poll();       api.boilerEmpty = false;       api.buttonPressed = true;       Poll();     }     [Test]     public void StartedPotNotEmpty()     {       NormalStart();       api.potNotEmpty = true;       Poll();       Assert.IsTrue(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsTrue(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void PotRemovedAndReplacedWhileEmpty()     {       NormalStart();       api.potPresent = false;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsFalse(api.valveClosed);       api.potPresent = true;       Poll();       Assert.IsTrue(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void PotRemovedWhileNotEmptyAndReplacedEmpty()     {       NormalFill();       api.potPresent = false;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsFalse(api.valveClosed);       api.potPresent = true;       api.potNotEmpty = false;       Poll();       Assert.IsTrue(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     private void NormalFill()     {       NormalStart();       api.potNotEmpty = true;       Poll();     }     [Test]     public void PotRemovedWhileNotEmptyAndReplacedNotEmpty()     {       NormalFill();       api.potPresent = false;       Poll();       api.potPresent = true;       Poll();       Assert.IsTrue(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsTrue(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void BoilerEmptyPotNotEmpty()     {       NormalBrew();       Assert.IsFalse(api.boilerOn);       Assert.IsTrue(api.lightOn);       Assert.IsTrue(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     private void NormalBrew()     {       NormalFill();       api.boilerEmpty = true;       Poll();     }     [Test]     public void BoilerEmptiesWhilePotRemoved()     {       NormalFill();       api.potPresent = false;       Poll();       api.boilerEmpty = true;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsTrue(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);       api.potPresent = true;       Poll();       Assert.IsFalse(api.boilerOn);       Assert.IsTrue(api.lightOn);       Assert.IsTrue(api.plateOn);       Assert.IsTrue(api.valveClosed);     }     [Test]     public void EmptyPotReturnedAfter()     {       NormalBrew  ();       api  .       potNotEmpty  = false;       Poll  ();       Assert.IsFalse(api.boilerOn);       Assert.IsFalse(api.lightOn);       Assert.IsFalse(api.plateOn);       Assert.IsTrue(api.valveClosed);     }   } }




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