Praise Demeter!

I want to take a moment to talk about a software design principal that's been significantly undersold perhaps especially in the ColdFusion community. It's called the Law of Demeter. Formally the Law of Demeter says that an object (CFC) method may only invoke or execute the methods of

  • the object
  • the object's properties (if they are components)
  • objects passed to the method as arguments
  • objects created within the current method

So if we're using the law of Demeter, viable code might look like this:

<cfcomponent displayname="user">
   <cffunction name="authenticate">
      <cfargument name="username" type="string" required="true" />
      <cfargument name="password" type="string" required="true" />
      <cfargument name="crypt" required="false" />

      <cfset var loginData = variables.dao.getLoginData(username) />
      <cfset var userAuth = CreateObject("component","userauth").init(loginData) />
      <cfset password = crypt.encryptPass(arguments.password) />
      <cfif userAuth.test(password)>
         <cfset variables.performLogin(userAuth) />
         <cfreturn true />
      <cfelse>
         <cfreturn false />
      </cfif>
   </cffunction>
</cfcomponent>

This isn't actually the way I would write this -- it's just for the demonstration.

  • invoking init() and test() on userAuth are okay because the component is created within the method
  • invoking crypt.encryptPass() is okay because crypt is passed to the method as an argument
  • invoking variables.dao.getLoginData() is okay because dao is a property of the current object
  • invoking variables.performLogin() is okay because it's a method of the current object

Here are some examples of code that would be "wrong" using the Law of Demeter:

<cfcomponent displayname="user">
   <cffunction name="authenticate">
      <cfargument name="username" type="string" required="true" />
      <cfargument name="password" type="string" required="true" />

      <cfset var userAuth = variables.dao.getLoginGateway().getLoginData(username) />
      <cfset password = request.crypt.encryptPass(arguments.password) />
      <cfif userAuth.test(password)>
         <cfset request.ServiceFactory.getLogin().PerformLogin(this) />
         <cfreturn true />
      <cfelse>
         <cfreturn false />
      </cfif>
   </cffunction>
</cfcomponent>

So here's what's "wrong":

  • invoking variables.dao.getLoginGateway().getLoginData() is wrong because the object returned from dao.getLoginGateway() is not a property of the current object
  • invoking request.crypt.encryptPass() is worse because it's jumping entirely outside the object hierarchy all together
  • invoking request.ServiceFactory.getLogin().performLogin() would then obviously be even worse yet again because it combines the previous two mistakes

More succinctly, the Law of Demeter says "only talk to your friends". As an analogy, the Chief of Naval Operations never gives orders to the crew on the deck of a ship, he gives his orders to the Admirals and they give their orders to the people under them. So the Law of Demeter seeks to create a similar chain of command within a software application.

I actually don't use the Law of Demeter formally. Why? Well because the formal definition of the Law of Demeter doesn't quite do what it was actually intended to do. This goes back to my last article about encapsulation. The idea is to reduce the amount of code required to achieve any given task (and thereby reducing the amount of maintenance the code requires) by reducing things as much as possible to Once and Only Once (OaOO) and Don't Repeat Yourself (DRY) structures.

Incidentally, DRY is also the reason I generally avoid code generators like Reactor, Illudium PU-36 or Transfer. I've watched people working with these things and struggling for days at a time to figure out how to make the code generator work the way they want where I'd have written something DRY and been done with it and moved on to the next thing in the first 8 hours. Code generators in general have always struck me a bit odd, given that they seem to me at least to run contrary to all the philosophies most devoutly espoused by the people responsible for inventing OO programming in the first place.

Anyway my revision of the Law of Demeter isn't very far from the official rules. Mine says you can call methods on objects returned from function calls within the current method, but you can't map out an object chain all at once. So for example:

GOOD:
<cfcomponent displayname="test">
   <cffunction name="doSomething">
      <cfset getSomeObject().doSomething() />
   </cffunction>
</cfcomponent>

BAD:
<cfcomponent displayname="test">
   <cffunction name="doSomething">
      <cfset getSomeObject().getSomethingElse().doSomething() />
   </cffunction>
</cfcomponent>

So if you needed to get doSomething() from the getSomethingElse() object, you would have to write the component like this instead:

<cfcomponent displayname="test">
   <cffunction name="getSomethingElse">
      <cfreturn getSomeObject().getSomethingElse() />
   </cffunction>

   <cffunction name="doSomething">
      <cfset getSomethingElse().doSomething() />
   </cffunction>
</cfcomponent>

Viola! The DoSomething function is now DRY because there's only one step in the function chain. This means any method within the current object that needs to call any function on the SomethingElse object can use getSomethingElse() and then if I need to change where SomethingElse comes from, I only have to change it in that one place. But because my components contain all these small methods like getSomethingElse() I also allow the getSomethingElse() methods to reference known core objects in applciation or request scopes, usually in a core component that's extended... So for example, you might see:

<cfcomponent displayname="base">
   <cffunction name="getSomethingElse">
      <cfreturn application.myFactory.getSomethingElse() />
   </cffunction>
</cfcomponent>

<cfcomponent displayname="test" extends="base">
   <cffunction name="doSomething">
      <cfset getSomeObject().doSomething() />
   </cffunction>
</cfcomponent>

This makes the code even more DRY because now all the objects that extend base.cfc will be able to get the SomethingElse object using the getSomethingElse() function -- so if I ever need to change where the factory is located, it's only referenced in that one place. And because this is so incredibly DRY, I prefer to use this method whenever possible instead of using CreateObject() to create and instantiate objects within the current method. Instead I'll only use CreateObject to instantiate a component in a method if the current object is a factory of some kind and then all other objects that might use it get it from the factory... but they all get to the factory via the DRY getFactory() type function inherited from something like base.cfc.

So actually I kinda lied before when I showed the code for the base object, because the base CFC should look like this:

<cfcomponent displayname="base">
   <cffunction name="getFactory">
      <cfreturn application.myFactory />
   </cffunction>

   <cffunction name="getSomethingElse">
      <cfreturn getFactory().getSomethingElse() />
   </cffunction>
</cfcomponent>

<cfcomponent displayname="factory">
   <cffunction name="getSomethingElse">
      <cfreturn CreateObject("component","somethingelse").init() />
   </cffunction>
</cfcomponent>

<cfcomponent displayname="test" extends="base">
   <cffunction name="doSomething">
      <cfset getSomethingElse().doSomething() />
   </cffunction>
</cfcomponent>

This means that if I want to change the location of the factory or of the SomethingElse object or want to change the way they're cached, etc, etc. I have just the one place to do that (instead of having my application littered with loads of references to application.myFactory that would all need to be changed). Above and beyond that, it also means less code in general because the application is also not littered with loads of identical lines of CreateObject() pointing to the same component path -- so for most objects, the path to the component object is only written in one CreateObject() instance in a factory and there's just one place to change that too... And above and beyond even that if I come across a situation in which a particular object needs to reference a modified or alternative version of the SomethingElse object, I can easily make it use whatever alternate or modified version it needs simply by overriding the 1-line getSomethingElse() method it inherited from the base CFC, without affecting any of the rest of the application.

Sweet! Because the alternatives to these are some of the biggest reasons why software becomes bloated and difficult to maintain. And this has analogies even to non-object code. Take for example, this block of typical ColdFusion code:

<cfif request.ProjectSetting[request.SiteURL][request.WebSiteMode]["packagedetail"][pk][ac].HasLeftMenu>
   <cfinclude template="#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].Dir#/#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk]['mainpage'].IncudePage#">
<cfelse>
   <cfif request.ssAdminModule and pk EQ "default">
      <cfoutput>#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].Dir#/#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].IncudePage#</cfoutput>   
      <cfabort>
   </cfif>
   <cfif not request.xmlContent and pk NEQ "default"><div id="containerText"></cfif>
      <cfinclude template="#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].Dir#/#request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].IncudePage#">   
   <cfif not request.xmlContent and pk NEQ "default"></div></cfif>
</cfif>

What a nightmare! You can't hardly do anything with this application because the application is littered with hundreds of references to request.ProjectSettings[request.SiteURL][request.WebSiteMode].packagedetail. But within any given page, the likelyhood of referencing anything in an alternate ProjectSettings structure is minimal at best. So this is the antithesis of DRY. And even if you're not going to turn these structures into objects, you should never EVER write code like this. You should instead at the top of the template/request/page (whatever you want to call it), do something like this:

<cfset CurrentProject = request.ProjectSettings[request.SiteURL][request.WebSiteMode] />
<cfset CurrentPackage = CurrentProject.packagedetail />

And here's one of the biggest problems with OO programming -- people have such a tough time understanding it... Because easily half the OO coders I've known, seeing the previous code sample, would have made this change:

Before:
<cfset x = request.ProjectSetting[request.SiteURL][request.WebSiteMode].packagedetail[pk][ac].Dir />

After:
<cfset x = request.ProjectSettings.getSite(request.siteURL).getMode(request.websitemode).getPackageDetail(pk).getAction(ac).getDir() />

If you've ever made a change like this, then you don't understand encapsulation and you don't understand DRY. It's not the end of the world, but I encourage you to take some time and learn why the above change is equally as bad as the code it replaces and why it would be much better to have this:

<cfset CurrentProject = request.ProjectSettings.getSite(request.SiteURL,request.WebSiteMode) />
...
<cfset CurrentPackage = CurrentProject.getPackage(pk) />
...
<cfset ThisAction = CurrentPackage.getAction(ac) />
...
<cfset x = ThisAction.getDir() />

And for those wondering why the Greek Mythology... well... Demeter's name seems to have something to do with "distribution of land" -- or in this case, "separation of concerns".

Comments
BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress