Monostate


The MONOSTATE pattern is another way to achieve singularity. It works through a completely different mechanism. We can see how that mechanism works by studying the Monostate test case in Listing 24-5.

The first test function simply describes an object whose x variable can be set and retrieved. But the second test case shows that two instances of the same class behave as though they were one. If you set the x variable on one instance to a particular value, you can retrieve that value by getting the x variable of a different instance. It's as though the two instances are simply different names for the same object.

Listing 24-5. Monostate test fixture

using NUnit.Framework; [TestFixture] public class TestMonostate {   [Test]   public void TestInstance()   {     Monostate m = new Monostate();     for (int x = 0; x < 10; x++)     {       m.X = x;       Assert.AreEqual(x, m.X);     }   }   [Test]   public void TestInstancesBehaveAsOne()   {     Monostate m1 = new Monostate();     Monostate m2 = new Monostate();     for (int x = 0; x < 10; x++)     {       m1.X = x;       Assert.AreEqual(x, m2.X);     }   } }

If we were to plug the Singleton class into this test case and replace all the new Monostate statements with calls to Singleton.Instance, the test case should still pass. So this test case describes the behavior of Singleton without imposing the constraint of a single instance!

How can two instances behave as though they were a single object? Quite simply, it means that the two objects must share the same variables. This is easily achieved by making all the variables static. Listing 24-6 shows the Monostate implementation that passes the preceding test case. Note that the itsX variable is static but that none of the methods are. This is important, as we'll see later.

Listing 24-6. Monostate implementation

public class Monostate {   private static int itsX;   public int X   {     get { return itsX; }     set { itsX = value; }   } }

I find this to be a delightfully twisted pattern. No matter how many instances of Monostate you create, they all behave as though they were a single object. You can even destroy or decommission all the current instances without losing the data.

Note that the difference between the two patterns is one of behavior versus structure. The SINGLETON pattern enforces the structure of singularity, preventing any more than one instance from being created. MONOSTATE, by contrast, enforces the behavior of singularity without imposing structural constraints. To underscore this difference, consider that the MONOSTATE test case is valid for the Singleton class but that the SINGLETON test case is not even close to being valid for the Monostate class.

Benefits

  • Transparency: Users do not behave differently from users of a regular object. The users do not need to know that the object is monostate.

  • Derivability: Derivatives of a monostate are monostates. Indeed, all the derivatives of a monostate are part of the same monostate. They all share the same static variables.

  • Polymorphism: Since the methods of a monostate are not static, they can be overridden in a derivative. Thus, different derivatives can offer different behavior over the same set of static variables.

  • Well-defined creation and destruction: The variables of a monostate, being static, have well-defined creation and destruction times.

Costs

  • No conversion: A nonmonostate class cannot be converted into a monostate class through derivation.

  • Efficiency: Because it is a real object, a monostate may go through many creations and destructions. These operations are often costly.

  • Presence: The variables of a monostate take up space, even if the monostate is never used.

  • Platform local: You can't make a monostate work across several CLR instances or across several platforms.

MONOSTATE in Action

Consider implementing the simple finite state machine (FSM) for the subway turnstile shown in Figure 24-1. The turnstile begins its life in the Locked state. If a coin is deposited, the turnstile transitions to the Unlocked state and unlocks the gate, resets any alarm state that might be present, and deposits the coin in its collection bin. If a user passes through the gate at this point, the turnstile transitions back to the Locked state and locks the gate.

Figure 24-1. Subway turnstile finite state machine


There are two abnormal conditions. If the user deposits two or more coins before passing through the gate, they will be refunded, and the gate will remain unlocked. If the user passes through without paying, an alarm will sound, and the gate will remain locked.

The test program that describes this operation is shown in Listing 24-7. Note that the test methods assume that the Turnstile is a monostate and expects to be able to send events and gather queries from different instances. This makes sense if there will never be more than one instance of the Turnstile.

The implementation of the monostate Turnstile is in Listing 24-8. The base Turnstile class delegates the two event functions, coin and pass, to two derivatives of Turnstile, Locked and Unlocked, that represent the states of the FSM.

Listing 24-7. TurnstileTest

using NUnit.Framework; [TestFixture] public class TurnstileTest {   [SetUp]   public void SetUp()   {     Turnstile t = new Turnstile();     t.reset();   }   [Test]   public void TestInit()   {     Turnstile t = new Turnstile();     Assert.IsTrue(t.Locked());     Assert.IsFalse(t.Alarm());   }   [Test]   public void TestCoin()   {     Turnstile t = new Turnstile();     t.Coin();     Turnstile t1 = new Turnstile();     Assert.IsFalse(t1.Locked());     Assert.IsFalse(t1.Alarm());     Assert.AreEqual(1, t1.Coins);   }   [Test]   public void TestCoinAndPass()   {     Turnstile t = new Turnstile();     t.Coin();     t.Pass();     Turnstile t1 = new Turnstile();     Assert.IsTrue(t1.Locked());     Assert.IsFalse(t1.Alarm());     Assert.AreEqual(1, t1.Coins, "coins");   }   [Test]   public void TestTwoCoins()   {     Turnstile t = new Turnstile();     t.Coin();     t.Coin();     Turnstile t1 = new Turnstile();     Assert.IsFalse(t1.Locked(), "unlocked");     Assert.AreEqual(1, t1.Coins, "coins");     Assert.AreEqual(1, t1.Refunds, "refunds");     Assert.IsFalse(t1.Alarm());   }   [Test]   public void TestPass()   {     Turnstile t = new Turnstile();     t.Pass();     Turnstile t1 = new Turnstile();     Assert.IsTrue(t1.Alarm(), "alarm");     Assert.IsTrue(t1.Locked(), "locked");   }   [Test]   public void TestCancelAlarm()   {     Turnstile t = new Turnstile();     t.Pass();     t.Coin();     Turnstile t1 = new Turnstile();     Assert.IsFalse(t1.Alarm(), "alarm");     Assert.IsFalse(t1.Locked(), "locked");     Assert.AreEqual(1, t1.Coins, "coin");     Assert.AreEqual(0, t1.Refunds, "refund");   }   [Test]   public void TestTwoOperations()   {     Turnstile t = new Turnstile();     t.Coin();     t.Pass();     t.Coin();     Assert.IsFalse(t.Locked(), "unlocked");     Assert.AreEqual(2, t.Coins, "coins");     t.Pass();     Assert.IsTrue(t.Locked(), "locked");   } }

Listing 24-8. Turnstile

public class Turnstile {   private static bool isLocked = true;   private static bool isAlarming = false;   private static int itsCoins = 0;   private static int itsRefunds = 0;   protected static readonly     Turnstile LOCKED = new Locked();   protected static readonly     Turnstile UNLOCKED = new Unlocked();   protected static Turnstile itsState = LOCKED;   public void reset()   {     Lock(true);     Alarm(false);     itsCoins = 0;     itsRefunds = 0;     itsState = LOCKED;   }   public bool Locked()   {     return isLocked;   }   public bool Alarm()   {     return isAlarming;   }   public virtual void Coin()   {     itsState.Coin();   }   public virtual void Pass()   {     itsState.Pass();   }   protected void Lock(bool shouldLock)   {     isLocked = shouldLock;   }   protected void Alarm(bool shouldAlarm)   {     isAlarming = shouldAlarm;   }   public int Coins   {     get { return itsCoins; }   }   public int Refunds   {     get { return itsRefunds; }   }   public void Deposit()   {     itsCoins++;   }   public void Refund()   {     itsRefunds++;   } } internal class Locked : Turnstile {   public override void Coin()   {     itsState = UNLOCKED;     Lock(false);     Alarm(false);     Deposit();   }   public override void Pass()   {     Alarm(true);   } } internal class Unlocked : Turnstile {   public override void Coin()   {     Refund();   }   public override void Pass()   {     Lock(true);     itsState = LOCKED;   } }

This example shows some of the useful features of the MONOSTATE pattern. It takes advantage of the ability for monostate derivatives to be polymorphic and the fact that monostate derivatives are themselves monostates. This example also shows how difficult it can sometimes be to turn a monostate into a nonmonostate. The structure of this solution strongly depends on the monostate nature of Turnstile. If we needed to control more than one turnstile with this FSM, the code would require some significant refactoring.

Perhaps you are concerned about the unconventional use of inheritance in this example. Having Unlocked and Locked derived from Turnstile seems a violation of normal OO principles. However, since Turnstile is a monostate, there are no separate instances of it. Thus, Unlocked and Locked aren't really separate objects but instead are part of the Turnstile abstraction. Unlocked and Locked have access to the same variables and methods that Turnstile does.




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