Sunday 28 February 2010

ColdFusion Head First Design Patterns: Observer

Continuing in this design patterns series taken from Head First Design Patterns, is a ColdFusion implementation of the Observer Pattern.

Interfaces

Subject.cfc

<cfinterface>
  <cffunction access="public" returntype="void" name="registerObserver">
    <cfargument required="true" type="Observer" name="o">
  </cffunction>

  <cffunction access="public" returntype="void" name="removeObserver">
    <cfargument required="true" type="Observer" name="o">
  </cffunction>
  
  <cffunction access="public" returntype="void" name="notifyObservers">
  </cffunction>
</cfinterface>

Observer.cfc

<cfinterface>
  <cffunction access="public" returntype="void" name="update">
    <cfargument required="true" type="numeric" name="temperature">
    <cfargument required="true" type="numeric" name="humidity">
    <cfargument required="true" type="numeric" name="pressure">        
  </cffunction>
</cfinterface>

DisplayElement.cfc

<cfinterface>
  <cffunction access="public" returntype="void" name="display">
  </cffunction>
</cfinterface>

Subject Implementation

WeatherData.cfc

<cfcomponent implements="Subject" output="false">

  <cffunction access="public" returntype="Subject" name="init">
    <cfset VARIABLES.observers = arrayNew(1)>
    
    <cfreturn THIS>
  </cffunction> 

  <cffunction access="public" returntype="void" name="registerObserver">
    <cfargument required="yes" type="Observer" name="o" />
    
    <cfset arrayAppend(VARIABLES.observers, ARGUMENTS.o)>
  </cffunction>

  <cffunction access="public" returntype="void" name="removeObserver">
    <cfargument required="yes" type="Observer" name="o" />
    
    <cfset arrayDelete(VARIABLES.observers, ARGUMENTS.o)>    
  </cffunction>

  <cffunction access="public" returntype="void" name="notifyObservers">
    <cfset var LOCAL = {}>
  
    <cfloop index="LOCAL.o" array="#VARIABLES.observers#">
      <cfset LOCAL.o.update(VARIABLES.temperature, VARIABLES.humidity, VARIABLES.pressure)>
    </cfloop>
  </cffunction>
  
  <cffunction access="public" returntype="void" name="measurementsChanged">
    <cfset notifyObservers()>
  </cffunction>
  
  <cffunction access="public" returntype="void" name="setMeasurements">
    <cfargument required="yes" type="numeric" name="temperature" />
    <cfargument required="yes" type="numeric" name="humidity" />
    <cfargument required="yes" type="numeric" name="pressure" />
    
    <cfset VARIABLES.temperature = ARGUMENTS.temperature>
    <cfset VARIABLES.humidity = ARGUMENTS.humidity>
    <cfset VARIABLES.pressure = ARGUMENTS.pressure>
    <cfset measurementsChanged()>
  </cffunction>
</cfcomponent>

Display Elements

CurrentConditionsDisplay.cfc

<cfcomponent implements="Observer, DisplayElement" output="true">

  <cffunction access="public" returntype="CurrentConditionsDisplay" name="init">
    <cfargument required="true" type="WeatherData" name="weatherData">
    
    <cfset VARIABLES.weatherData = ARGUMENTS.weatherData>
    <cfset VARIABLES.weatherData.registerObserver(THIS)>
    
    <cfreturn THIS>
  </cffunction>

  <cffunction access="public" returntype="void" name="update">
    <cfargument required="true" type="numeric" name="temperature">
    <cfargument required="true" type="numeric" name="humidity">
    <cfargument required="true" type="numeric" name="pressure">
    
    <cfset VARIABLES.temperature = ARGUMENTS.temperature>
    <cfset VARIABLES.humidity = ARGUMENTS.humidity>
    <cfset display()>
  </cffunction>

  <cffunction access="public" returntype="void" name="display">
    <cfoutput>
      Current conditions: #VARIABLES.temperature#F degrees and #VARIABLES.humidity#% humidity
      <br />
    </cfoutput>
  </cffunction>
</cfcomponent>

HeatIndexDisplay.cfc

<cfcomponent implements="Observer, DisplayElement" output="true">

  <cffunction access="public" returntype="HeatIndexDisplay" name="init">
    <cfargument required="true" type="WeatherData" name="weatherData">
    
    <cfset VARIABLES.weatherData = ARGUMENTS.weatherData>
    <cfset VARIABLES.weatherData.registerObserver(THIS)>
    
    <cfreturn THIS>
  </cffunction>

  <cffunction access="public" returntype="void" name="update">
    <cfargument required="true" type="numeric" name="temperature">
    <cfargument required="true" type="numeric" name="humidity">
    <cfargument required="true" type="numeric" name="pressure">
    
    <cfset VARIABLES.temperature = ARGUMENTS.temperature>
    <cfset VARIABLES.humidity = ARGUMENTS.humidity>
    <cfset VARIABLES.heatIndex = computeHeatIndex(VARIABLES.temperature, VARIABLES.humidity)>
    <cfset display()>
  </cffunction>

  <cffunction access="public" returntype="void" name="display">
    <cfoutput>
      Heat index is #VARIABLES.heatIndex#
      <br />
    </cfoutput>
  </cffunction>

  <cffunction access="private" returntype="numeric" name="computeHeatIndex">
    <cfargument required="true" type="numeric" name="t">
    <cfargument required="true" type="numeric" name="rh">

    <cfset var LOCAL = {}>
    <cfset LOCAL.index = ((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) +
            (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) +
            (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
            (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *  
            (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
            (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +     
            0.000000000843296 * (t * t * rh * rh * rh)) -
            (0.0000000000481975 * (t * t * t * rh * rh * rh)))>
  
    <cfreturn LOCAL.index>
  </cffunction> 
</cfcomponent>

Test Page

WeatherStation.cfm

<cfset weatherData = createObject("component", "WeatherData").init()>

<cfset currentDisplay = createObject("component", "CurrentConditionsDisplay").init(weatherData)>
<cfset heatDisplay = createObject("component", "HeatIndexDisplay").init(weatherData)>

<cfset weatherData.setMeasurements(80, 65, 30.4)>
<cfset weatherData.setMeasurements(82, 70, 29.2)>
<cfset weatherData.setMeasurements(78, 90, 29.2)>

Source Code