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:
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.
On the elements level, expose the new “Accept(…)” methods to pass into it the Visitors that will support the required logic.
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!
The ISmartDevice interface that will expose Accept(…) method to pass himself on to the Visitor.
The IVisitorItem interface that will expose the supported visit types.
Now let’s take a look on the SmartPhone Class implementation.
And the implementation of ComputePriceVisitor as one of the Visitors.
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 🙂