Visitor Pattern

April 21, 2014

After I wrote the previous post I understand that must of the time by accident we couple the logic to the model too much, and that all in the name of capsulation.

Let’s take for example the next Classes:

VisitorPattern_1

We can see here 3 concrete Classes that diverted from SmartDevice Class. Tables, Phones & Watches.

As you can see I reviled 4 methods here (I you can imagine there are more than that), By from my point of view under 2 categories:

  • Close Method: No change on the logic, no extension required
    • Support3G: By the device spec, or it’s supported of  not
    • MemorySize: The same as Support3G
  • Circumstantial Method: That mean that on this specific point answer the given question, on future an extension will be required
    • Price: The Price logic can be change to add Bonus or Discount or any other new thing to market the device (right now or later when the device will not be so popular anymore)
    • AvailableOnNetworks: Also here and Networks or old that will not support it anymore. Cooperation between Networks, or any other logic that will need to update / extended to answer this question.

The problem is; Why open the Class to change this logic or extend it? or in other words. Why the Class definition (Methods & Variables) so coupled with the logic implementation? Why we must compile all Class when adding new logic that will impact one or two of them?

The Answer is very simple; it’s should not be!!! The Module capsulation talk about “Be responsible of all Module aspect on single area”, But we (The developers) sometimes do it wrong.

Meet the Visitor Pattern

The main idea is to package related functionality on the same “Visitor” (the “Price()” functionality of the Tablet, Smartphone & Smartwatch) and expose on the concrete Class (A.K.A. element) an “Accept(Visitor v)” method. When the element get the Visitor and Accept it, it passing himself to the Visitor. The Visitor that get the element execute the required method.

Visitor Pattern

image

On the elements level, expose the new “Accept(…)” methods to pass into it the Visitors that will support the required logic.

image

On the “Visitors” side we should create new Visitor for each supported Logic and override the visiting elements that exist in the system.

For example the ComputePriceVisitor have 3 Visit methods for each of the Smart devices types and two fields for the Price & Discount percentage.

*** Note; the VisitorItem abstract Class can also be an Interface!

Code Example

The ISmartDevice interface that will expose Accept(…) method to pass himself on to the Visitor.

   1: public interface ISmartDevice{

   2:

   3:     void Accept(IVisitorItem);

   4:

   5: }

The IVisitorItem interface that will expose the supported visit types.

   1:

public

 interface IVisitorItem{

   2:

   3:     void VisitTablate(ISmartDevice sd);

   4:     void VisitSmartPhone(ISmartDevice sd);

   5:     void VisitSmartWatch(ISmartDevice sd);

   6:

   7:     //Also possible

   8:     // void Visit (Tablate t);

   9:     // ... and so on

  10: }

Now let’s take a look on the SmartPhone Class implementation.

   1: public class SmartPhone:ISmartDevice{

   2:

   3:     public SmartPhone(string name){

   4:

   5:         _name=name;

   6:     }

   7:

   8:     public override void Accept(IVisitorItem visitor){

   9:

  10:         visitor.VisitSmartPhone(this);

  11:     }

  12:

  13:     public string Name{

  14:

  15:         get{return _name;}

  16:     }

  17:     public string Description {get;set;}

  18:

  19: }

And the implementation of ComputePriceVisitor as one of the Visitors.

   1: public class ComputePriceVisitor:IVisitorItem{

   2:

   3:     private float _price=0.0f;

   4:

   5:     public float Price{ get{ return _price;}}

   6:

   7:     public ComputePriceVisitor(ISmartDevice device){

   8:

   9:         InternalComputePrice(device);

  10:     }

  11:

  12:     private InternalComputePrice(ISmartDevice device){

  13:

  14:         /// switch by device type

  15:         /// add discount

  16:         ///add promotions

  17:         ///...

  18:     }

  19: }

What left to do is on creation is to set the Visitors for each element. Element can have one or more Visitors that can be fixed on creation or using IoC to do so. Or any other configuration type (Database, configuration file, etc).

The beauty in all  of that is, that the elements Classes  (Devices, SmartPhone /  Tablet / SmartWatch / …) located on different DLL / JAR / LIB file and we should not touch it and recompile it for logic that can (and must of the time will) be change. We have here total separation between to logic and the Class definition and we maintained here real S.O.L.I.D. implementation to avoid cross changes and impacts.

Take in maid next time you design your application, it is not the only solution but it’s one of them 🙂

Amour Shmuel

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*