Visitor


Consider the Modem hierarchy in Figure 35-1. The Modem interface contains the generic methods that all modems can implement. Three derivatives are shown: one that drives a Hayes modem, one that drives a Zoom modem, and one that drives the modem card produced by Ernie, one of our hardware engineers. How can we configure these modems for UNIX without putting the ConfigureForUnix method in the Modem interface? We can use a technique called dual dispatch, the mechanism at the heart of the VISITOR pattern.

Figure 35-1. Modem hierarchy


Figure 35-2 shows the VISITOR structure, and Listings 35-1 through 35-5 show the corresponding C# code. Listing 35-6 shows the test code that both verifies that VISITOR works and demonstrates how another programmer should use it.

Figure 35-2. Visitor


Note that the visitor hierarchy has a method in for every derivative of the visited (Modem) hierarchy. This is a kind of 90° rotation: from derivatives to methods.

The test code shows that to configure a modem for UNIX, a programmer creates an instance of the UnixModemConfigurator class and passes it to the Accept function of the Modem. The appropriate Modem derivative will then call Visit(this) on Modem-Visitor, the base class of UnixModemConfigurator. If that derivative is a Hayes, Visit(this) will call public void Visit(Hayes), which will deploy to the public void Visit(Hayes) function in UnixModemConfigurator, which then configures the Hayes modem for Unix.

Listing 35-1. Modem.cs

public interface Modem {   void Dial(string pno);   void Hangup();   void Send(char c);   char Recv();   void Accept(ModemVisitor v); }

Listing 35-2. HayesModem.cs

public class HayesModem : Modem {   public void Dial(string pno){}   public void Hangup(){}   public void Send(char c){}   public char Recv() {return (char)0;}   public void Accept(ModemVisitor v) {v.Visit(this);}   public string configurationString = null; }

Listing 35-3. ZoomModem.cs

public class ZoomModem {   public void Dial(string pno){}   public void Hangup(){}   public void Send(char c){}   public char Recv() {return (char)0;}   public void Accept(ModemVisitor v) {v.Visit(this);}   public int configurationValue = 0; }

Listing 35-4. ErnieModem.cs

public class ErnieModem {   public void Dial(string pno){}   public void Hangup(){}   public void Send(char c){}   public char Recv() {return (char)0;}   public void Accept(ModemVisitor v) {v.Visit(this);}   public string internalPattern = null; }

Listing 35-5. UnixModemConfigurator.cs

public class UnixModemConfigurator : ModemVisitor {   public void Visit(HayesModem m)   {     m.configurationString = "&s1=4&D=3";   }   public void Visit(ZoomModem m)   {     m.configurationValue = 42;   }   public void Visit(ErnieModem m)   {     m.internalPattern = "C is too slow";   } }

Listing 35-6. ModemVisitorTest.cs

[TestFixture] public class ModemVisitorTest {   private UnixModemConfigurator v;   private HayesModem h;   private ZoomModem z;   private ErnieModem e;   [SetUp]   public void SetUp()   {     v = new UnixModemConfigurator();     h = new HayesModem();     z = new ZoomModem();     e = new ErnieModem();   }   [Test]   public void HayesForUnix()   {     h.Accept(v);     Assert.AreEqual("&s1=4&D=3", h.configurationString);   }   [Test]   public void ZoomForUnix()   {     z.Accept(v);     Assert.AreEqual(42, z.configurationValue);   }   [Test]   public void ErnieForUnix()   {     e.Accept(v);     Assert.AreEqual("C is too slow", e.internalPattern);   } }

Having built this structure, new operating system configuration functions can be added by adding new derivatives of ModemVisitor without altering the Modem hierarchy in any way. So the VISITOR pattern substitutes derivatives of ModemVisitor for methods in the Modem hierarchy.

This dual dispatch involves two polymorphic dispatches. The first is the Accept function, which resolves the type of the object that Accept is called on. The second dispatchthe Visit method called from the resolved Accept methodresolves to the particular function to be executed.

The two dispatches of VISITOR form a matrix of functions. In our modem example, one axis of the matrix is the various types of modems; the other axis, the various types of operating systems. Every cell in this matrix is filled in with a function that describes how to initialize the particular modem for the particular operating system.

VISITOR is fast. It requires only two polymorphic dispatches, regardless of the breadth or depth of the visited hierarchy.




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