Decorator


The VISITOR pattern gave us a way to add methods to existing hierarchies without changing those hierarchies. Another pattern that accomplishes this is DECORATOR.

Consider once again the Modem hierarchy in Figure 35-1. Imagine that we have an application that has many users. Sitting at a computer, each user can ask the system to call out to another computer, using the computer's modem. Some of the users like to hear their modem's dial. Others like their modems to be silent.

We could implement this by querying the user preferences at every location in the code where the modem is dialed. If the user wants to hear the modem, we set the speaker volume high; otherwise, we turn it off:

... Modem m = user.Modem; if (user.WantsLoudDial())   m.Volume = 11; // it's one more than 10, isn't it? m.Dial(...); ...


The specter of seeing this stretch of code duplicated hundreds of times throughout the application conjures images of 80-hour weeks and heinous debugging sessions. It is something to be avoided.

Another option would be to set a flag in the modem object itself and have the Dial method inspect it and set the volume accordingly:

... public class HayesModem : Modem {   private bool wantsLoudDial = false;   public void Dial(...)   {     if (wantsLoudDial)     {       Volume = 11;     }     ...   }   ... }


This is better but must still be duplicated for every derivative of Modem. Authors of new derivatives of Modem must remember to replicate this code. Depending on programmers' memories is pretty risky business.

We could resolve this with the TEMPLATE METHOD[3] pattern by changing Modem from an interface to a class, having it hold the wantsLoudDial variable, and having it test that variable in the dial function before it calls the DialForReal function:

[3] See Chapter 22.

... public abstract class Modem {   private bool wantsLoudDial = false;   public void Dial(...)   {     if (wantsLoudDial)     {       Volume = 11;     }     DialForReal(...)   }   public abstract void DialForReal(...); }


This is better still, but why should Modem be affected by the whims of the user in this way? Why should Modem know about loud dialing? Must it then be modified every time the user has some other odd request, such as logging out before hangup?

Once again, the Common Closure Principle (CCP) comes into play. We want to separate those things that change for different reasons. We can also invoke the Single-Responsibility Principle (SRP), since the need to dial loudly has nothing to do with the intrinsic functions of Modem and should therefore not be part of Modem.

DECORATOR solves the issue by creating a completely new class: LoudDialModem. LoudDialModem derives from Modem and delegates to a contained instance of Modem, catching the Dial function and setting the volume high before delegating. Figure 35-5 shows the structure.

Figure 35-5. DECORATOR: LoudDialModem


Now the decision to dial loudly can be made in one place. At the place in the code where the user sets preferences, a LoudDialModem can be created if loud dialing is requested, and the user's modem can be passed into it. LoudDialModem will delegate all calls made to it to the user's modem, so the user won't notice any difference. The Dial method, however, will first set the volume high before delegating to the user's modem. The LoudDialModem can then become the user's modem without anybody else in the system being affected. Listings 35-24 through 35-27 show the code.

Listing 35-24. Modem.cs

public interface Modem {   void Dial(string pno);   int SpeakerVolume { get; set; }   string PhoneNumber { get; } }

Listing 35-25. HayesModem.cs

public class HayesModem : Modem {   private string phoneNumber;   private int speakerVolume;   public void Dial(string pno)   {     phoneNumber = pno;   }   public int SpeakerVolume   {     get { return speakerVolume; }     set { speakerVolume = value; }   }   public string PhoneNumber   {     get { return phoneNumber; }   } }

Listing 35-26. LoudDialModem.cs

public class LoudDialModem : Modem {   private Modem itsModem;   public LoudDialModem(Modem m)   {     itsModem = m;   }   public void Dial(string pno)   {     itsModem.SpeakerVolume = 10;     itsModem.Dial(pno);   }   public int SpeakerVolume   {     get { return itsModem.SpeakerVolume; }     set { itsModem.SpeakerVolume = value; }   }   public string PhoneNumber   {     get { return itsModem.PhoneNumber; }   }

Listing 35-27. ModemDecoratorTest.cs

[TestFixture] public class ModemDecoratorTest {   [Test]   public void CreateHayes()   {     Modem m = new HayesModem();     Assert.AreEqual(null, m.PhoneNumber);     m.Dial("5551212");     Assert.AreEqual("5551212", m.PhoneNumber);     Assert.AreEqual(0, m.SpeakerVolume);     m.SpeakerVolume = 10;     Assert.AreEqual(10, m.SpeakerVolume);   }   [Test]   public void LoudDialModem()   {     Modem m = new HayesModem();     Modem d = new LoudDialModem(m);     Assert.AreEqual(null, d.PhoneNumber);     Assert.AreEqual(0, d.SpeakerVolume);     d.Dial("5551212");     Assert.AreEqual("5551212", d.PhoneNumber);     Assert.AreEqual(10, d.SpeakerVolume);   } }

Sometimes, two or more decorators may exist for the same hierarchy. For example, we may wish to decorate the Modem hierarchy with LogoutExitModem, which sends the string 'exit' whenever the Hangup method is called. This second decorator will have to duplicate all the delegation code that we have already written in LoudDialModem. We can eliminate this duplicate code by creating a new class, ModemDecorator, that supplies all the delegation code. Then the actual decorators can simply derive from ModemDecorator and override only those methods that they need to. Figure 35-6 and Listings 35-28 and 35-29 show the structure.

Figure 35-6. ModemDecorator


Listing 35-28. ModemDecorator.cs

public class ModemDecorator {   private Modem modem;   public ModemDecorator(Modem m)   {     modem = m;   }   public void Dial(string pno)   {     modem.Dial(pno);   }   public int SpeakerVolume   {     get { return modem.SpeakerVolume; }     set { modem.SpeakerVolume = value; }   }   public string PhoneNumber   {     get { return modem.PhoneNumber; }   }   protected Modem Modem   {     get { return modem; }   } }

Listing 35-29. LoudDialModem.cs

public class LoudDialModem : ModemDecorator {   public LoudDialModem(Modem m) : base(m)   {}   public void Dial(string pno)   {     Modem.SpeakerVolume = 10;     Modem.Dial(pno);   } }




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