Posts with the tag 'example'


Strategy Design Pattern in Coldfusion

In this blog post I will demonstrate the practical use of strategy design pattern in ColdFusion and discuss other possibilities.

So what is Strategy Design Pattern?
Wikipedia : “In computer programming, the strategy pattern (also known as the policy pattern) is a particular software design pattern, whereby algorithms can be selected at runtime.
Gang of four : “Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

So from the various descriptions it’s clear that this pattern can be used in the situation where there is possibility of having difference/multiple calculation and the application has to choose the right one on the fly.

I am going to use a scenario to build the case on why and how this pattern should be used.

Scenario:

Let’s say there is a web application which has multiple developers working on it, or have multiple teams working on independent section of the application, in such sort of application its fundamental to have core set of components that the developer use and there is also a possibility that they may be used across multiple applications. In such sort of scenario its inherent that core component be stable and least amount of changes should be done on the core components (i.e. cfc’s ) to avoid large scale impacts to other modules/developers.

So let’s say one of the core components that we have is the Customer.cfc it has some properties and also methods which get used by the appropriate section of the web applications


example1.jpg

<cfcomponent name="Customer" output="false">
  <cfscript>
    this.name = "";
    this.address = "";
    this.customerType = 0;
    this.balanceDue = 0;
  </cfscript>
</cfcomponent>
<!--- 
  ideally all the attributes should be in variables scope and have a get/set method defined, 
  but i am skipping that portion, so that i can focus on strategy pattern, and to have 
  less code :-) 
--->

There came a request for business to also start storing the latitude and longitude of customer location in the database and it was decided that we are going to use Google’s reverse lookup service, everybody agreed and the core Customer.cfc got updated to
example2.jpg

<cfcomponent name="Customer" output="false">
  <cfscript>
    this.name = "";
    this.address = "";
    this.customerType = 0;
    this.balanceDue = 0;
    this.latitude = 0;
    this.longitude = 0;
  </cfscript>
  
  <cffunction name="reverseLookup">
    <!--- 
      perform the reverse geocode with google  and return the values back as a string
      and also update the latitude and logitude attribute
    --->
    <cfreturn "">
  </cffunction>
</cfcomponent>

Here you can see two new properties were added and a method was added to perform the reverse lookup.

This worked fine, until business came back and said that they are not pleased with Google’s service and want to use yahoo, the simpler solution would be just remove the Google specific code and replace it with yahoo code, but the architect decided why not have both in place, who know the business may change their mind, so the code updated to.

example3.jpg

<cfcomponent name="Customer" output="false">
  <cfscript>
    this.name = "";
    this.address = "";
    this.customerType = 0;
    this.balanceDue = 0;
    this.latitude = 0;
    this.longitude = 0;
  </cfscript>
  
  <cffunction name="reverseLookup">
    <cfargument name="serviceID" type="numeric" required="true">
    <cfif serviceID EQ 1>
      <!--- google code--->
    <cfelseif serviceID EQ 2>
      <!--- yahoo code--->
    </cfif>
    <cfreturn "">
  </cffunction>
</cfcomponent>

As you can see the core component introduced the notion of serviceID, where the developers or consumer of this component has to supply the service ID i.e. yahooID or googleID. The change was made but it involved much pain because all the developers has to be notified about it and also the other applications that were using the component had to made updates because the interface for the core component got changed.

Now after some time, the business came up with another suggestion they had found this database that can be bought which has all the required data to perform the reverse lookup without going to any third party and wanted the development team to implement it. To accommodate this request the core component had to be again updated, with an additional IF condition
<cfcomponent name="Customer" output="false">
  <cfscript>
    this.name = "";
    this.address = "";
    this.customerType = 0;
    this.balanceDue = 0;
    this.latitude = 0;
    this.longitude = 0;
  </cfscript>
  
  <cffunction name="reverseLookup">
    <cfargument name="serviceID" type="numeric" required="true">
    <cfif serviceID EQ 1>
      <!--- google code--->
    <cfelseif serviceID EQ 2>
      <!--- yahoo code--->
    <cfelseif serviceID EQ 3>
      <!--- db code--->
    </cfif>
    <cfreturn "">
  </cffunction>
</cfcomponent>

As you can see in every situation the core component gets effected, i.e. any time a new vendor is found the core component has to be updated with additional IF logic, which means the core component has to be touched every time and developed team really does not want to do that because of downside impact that may happened if a bug got introduced during the change.

How does Strategy Pattern Solves the above problem?

Strategy Pattern is the perfect solution for the above mentioned scenario, where first of the the calculation/algorithm (i.e. the reverse geocoding) part of the code, gets split into a component of its own, so there will be a .cfc file for each of the reverse geocoding i.e. googleReversGeocoding.cfc , yahooReversGeocoding.cfc and databaseReversGeocoding.cfc and they all will inherit a single interface to perform decoding

example4.jpg
example5.jpg
IReverseLookup.cfc

<cfinterface name="IReverseLookup">
  <cffunction name="decode" returntype="string">
    <cfargument name="address" type="String" required="true">
  </cffunction>
</cfinterface>

GoogleReverseLookup.cfc

<cfcomponent name="GoogleReverseLookup" implements="IReverseLookup">
  <cffunction name="decode" returntype="String">
    <cfargument name="address" type="String" required="true">
    <!--- 
      make calls to google 
      and return the data
    --->
    <cfreturn "1929202.111, 292920.211">
  </cffunction>
</cfcomponent>

YahooReverseLookup.cfc

<cfcomponent name="YahooReverseLookup" implements="IReverseLookup">
  <cffunction name="decode" returntype="String">
    <cfargument name="address" type="String" required="true">
    <!--- 
      make calls to yahoo
      and return the data
    --->
    <cfreturn "0119182781.111, 5111192929.211">
  </cffunction>
</cfcomponent>

DatabaseReverseLookup.cfc

<cfcomponent name="DatabaseReverseLookup" implements="IReverseLookup">
  <cffunction name="decode" returntype="String">
    <cfargument name="address" type="String" required="true">
    <!--- 
      make calls to Database
      and return the data
    --->
    <cfreturn "000002221, 0028289.857">
  </cffunction>
</cfcomponent>

DatabaseReverseLookup.cfc

<cfcomponent name="Customer" output="false">
  <cfscript>
    this.name = "";
    this.address = "";
    this.customerType = 0;
    this.balanceDue = 0;
    this.latitude = 0;
    this.longitude = 0;
  </cfscript>
  
  <cffunction name="reverseLookup">
    <cfargument name="reversLookupStrategy" type="IReverseLookup" required="true">
    <cfreturn reversLookupStrategy.decode(this.address)>
  </cffunction>
</cfcomponent>

example1.cfm

<cfset customer = CreateObject("component", "Customer")>
<cfset customer.address = "410 washington Street, Greely, NC 10001">
<!--- set other properties --->

<!--- code wasts to use google service --->
<cfset strategy = CreateObject("component", "GoogleReverseLookup")>
<cfoutput>#customer.reverseLookup(strategy)#<br></cfoutput>

<!--- code wasts to use yahoo service --->
<cfset strategy = CreateObject("component", "YahooReverseLookup")>
<cfoutput>#customer.reverseLookup(strategy)#<br></cfoutput>

<!--- code wasts to use database service --->
<cfset strategy = CreateObject("component", "DatabaseReverseLookup")>
<cfoutput>#customer.reverseLookup(strategy)#<br></cfoutput>

So with the strategy pattern, now we will have cleaner implementation, where decoding with each vendor/service is done separately and Customer class (cfc) does not care about what vendor we are using as long the object that is being passed to it implements the IReverseLookup interface then that’s all it cares about. With this type of implementation you can add as my vendor and defined the logic in separate CFC (implement the interface) and you will never have to be updated the customer object. This way the code that is using this component can at runtime decide what strategy i.e. calculation to use without effecting the core component.

When can you use the strategy pattern be used?

a) If you have multiple components (CFC) that have structural similarity , but have different behavior (due diligence has to be done)
b) You have algorithm whose calculation can vary depending up the parameters passed to it.
c) Have excessive use of switch or if statement, and you have a desire to clear it up ?
d) You don’t want your core component to updated every time a new strategy is developed or requested.

Download Source Code

1 comment June 28th, 2008


 Subscribe in a RSS reader

Tags

Calendar

November 2008
M T W T F S S
« Jun    
 12
3456789
10111213141516
17181920212223
24252627282930

Posts by Month

Posts by Category