OOP Terminology : Part V - Composition & Aggregation
October 10, 2008
The so called "big 3" of OO are generally accepted to be encapsulation, polymorphism, and inheritance. I've mentioned those in my previous posts, so now it's the turn of composition and aggregation.
So what is composition?
Object Composition is when you group a bunch of objects together to form a more complex object. Wikipedia uses the example of a car (or automobile if you prefer!). A car is composed of the objects; wheel, engine, seat, frame. On there own they don't do much, but put them together we have something rather useful. The relationship been the car object and it's parts is commonly referred to as a "Has-A" relationship. A car "Has-A" engine.
Taking this thought a step further it is safe to say that a car can not exist without it's composite parts. This is where composition varies from the similar term aggregation.
So what is aggregation?
When a object can only exists if it has all of it's composite parts it is called a composed object (or composited object). An aggregated object can reference other objects, but it doesn't need them. For example; a person "Has-A" address but if they were made homeless that person would still exist.
So, how do we build a "Has-A" relationship between two CFCs?
Continuing with the Person example let's create a person class and an address class. Remember a class is a CFC for us Cfers!
Person.cfc
<cfcomponent hint="I represent a person">
<cffunction name="init" hint="I construct a person">
<cfargument name="firstname" required="true" />
<cfargument name="lastname" required="true" />
<cfargument name="dateofbirth" required="true" />
<cfargument name="address" required="false" />
<cfscript>
variables.instance = StructNew();
If ( StructKeyExists( arguments, "address" ) ) {
setAddress( arguments.address );
}
setFirstName( arguments.firstname );
setLastName( arguments.lastname );
setDateOfBirth( arguments.dateofbirth );
</cfscript>
<cfreturn this />
</cffunction>
<!--- methods --->
<cffunction name="hasAbode"
access="public"
returntype="boolean">
<cfreturn StructKeyExists( variables.instance, "address" ) />
</cffunction>
<!--- public set accessors (aka mutators) --->
<cffunction name="setAddress"
access="public"
returntype="void">
<cfargument name="address" required="true" />
<cfset variables.instance.address = arguments.address />
</cffunction>
<cffunction name="setFirstname"
access="public"
returntype="void">
<cfargument name="firstname" required="true" />
<cfset variables.instance.firstname = arguments.firstname />
</cffunction>
<cffunction name="setLastname"
access="public"
returntype="void">
<cfargument name="lastname" required="true" />
<cfset variables.instance.lastname = arguments.lastname />
</cffunction>
<!--- public get accessors (aka mutators) --->
<cffunction name="getAddress"
access="public"
returntype="address">
<cfreturn variables.instance.address />
</cffunction>
<cffunction name="getFullname"
access="public"
returntype="string">
<cfreturn variables.instance.firstname & " " & variables.instance.lastname />
</cffunction>
<!--- private set accessors (aka mutators) --->
<cffunction name="setDateOfBirth"
access="private"
returntype="void">
<cfargument name="dateofbirth" required="true" />
<cfset variables.instance.dateofbirth = arguments.dateofbirth />
</cffunction>
</cfcomponent>
Address.cfc
<cfcomponent hint="I represent an address">
<cffunction name="init" hint="I construct a person">
<cfargument name="street" required="true" />
<cfargument name="city" required="true" />
<cfargument name="county" required="true" />
<cfargument name="postcode" required="true" />
<cfscript>
variables.instance = StructNew();
setStreet( arguments.street );
setCity( arguments.city );
setCounty( arguments.county );
setPostcode( arguments.postcode );
</cfscript>
<cfreturn this />
</cffunction>
<!--- set accessors (aka mutators) --->
<cffunction name="setStreet"
access="public"
returntype="void">
<cfargument name="street" required="true" />
<cfset variables.instance.street = arguments.street />
</cffunction>
<cffunction name="setCity"
access="public"
returntype="void">
<cfargument name="city" required="true" />
<cfset variables.instance.city = arguments.city />
</cffunction>
<cffunction name="setCounty"
access="public"
returntype="void">
<cfargument name="county" required="true" />
<cfset variables.instance.county = arguments.county />
</cffunction>
<cffunction name="setPostcode"
access="public"
returntype="void">
<cfargument name="postcode" required="true" />
<cfset variables.instance.postcode = arguments.postcode />
</cffunction>
<!--- get accessors (aka mutators) --->
<cffunction name="getStreet"
access="public"
returntype="string">
<cfreturn variables.instance.street />
</cffunction>
<cffunction name="getCity"
access="public"
returntype="string">
<cfreturn variables.instance.city />
</cffunction>
<cffunction name="getCounty"
access="public"
returntype="string">
<cfreturn variables.instance.county />
</cffunction>
<cffunction name="getPostcode"
access="public"
returntype="string">
<cfreturn variables.instance.postcode />
</cffunction>
</cfcomponent>
OK, now let us write some code to instantiate our class and create our people and address objects.
<cfset Person = CreateObject( "component", "path-to-cfc.Person").init( firstname="John", lastname="Whish", dateofbirth=CreateDate( 1975, 1, 1 ) ) /> <cfoutput> Fullname:<br /> #Person.getFullname()#<br /> <cfif Person.hasAbode()> Address:<br /> #Person.getAddress().getStreet()#<br /> #Person.getAddress().getCity()#<br /> #Person.getAddress().getCounty()#<br /> #Person.getAddress().getPostcode()#<br /> <cfelse> No Fixed Abode<br /> </cfif> </cfoutput>
When we run the code above it will show:
Fullname: John Whish No Fixed Abode
Let's try again, but this time pass in an address object when we instantiate our person class.
<cfset BirthPlace = CreateObject( "component", "path-to-cfc.Address" ).init( street="1 Some Street", city="Mirfield", county="Yorkshire", postcode="YO1 1AB" ) /> <cfset Person = CreateObject( "component", "path-to-cfc.Person" ).init( firstname="Patrick", lastname="Stewart", dateofbirth=CreateDate( 1940, 7, 13 ), address=Abode ) /> <cfoutput> Fullname:<br /> #Person.getFullname()#<br /> <cfif Person.hasAbode()> Address:<br /> #Person.getAddress().getStreet()#<br /> #Person.getAddress().getCity()#<br /> #Person.getAddress().getCounty()#<br /> #Person.getAddress().getPostcode()#<br /> <cfelse> No Fixed Abode<br /> </cfif> </cfoutput>
When we run the code above it will show:
Fullname: Patrick Stewart Address: 1 Some Street Mirfield Yorkshire YO1 1AB
Some other things to consider
If you are developing your code for ColdFusion 8, then you can save a lot of typing by using onMissingMethod to replace all of your accessors (the getters and setters). I've chosen to write it out the long way so for clarity and also so that we can have a nice API for our objects. Once you are familiar with how accessors work then it is well worth having a look at using onMissingMethod to add some dynamic code to your objects.
I've made most of the accessors public, this means that we can call them directly from outside our class. The chances are that someone won't change their name but they could once they exist (we've created them with the init method) so we need to be able to change their name without destroying our instance of the person class first, as that would be a bit extreme! By the same token they can change their address but not their date of birth.
Whilst getters and setters are useful, it is tempting to overuse them. Objects shouldn't just have get and set methods, in my example I've decided to create a hasAbode() method instead of just checking in my calling code if to see if Person.getAddress().getStreet() has any data.
ColdFusion is a dynamically typed language, which means that we don't have to specify the data type that a method accepts or returns. This is a whole new subject but I'd encourage you to look up "Duck Typing" in your favourite search engine :)
Another thing you can do with ColdFusion 8 is make use of the argumentCollection feature to make your code more readable. Here is a quick example:
<cfscript>
PersonProperties = {};
PersonProperties.firstname = "Patrick";
PersonProperties.lastname = "Stewart";
PersonProperties.dateofbirth=CreateDate( 1940, 7, 13 );
Person = CreateObject( "component", "com.Person" ).init( argumentcollection = PersonProperties );
</cfscript>
I'm by no means an OO guru, this is just how I understand it so hopefully I've given good advice! This is post number 5 in my mini series. Corrections and comments always welcome - in fact just let me know that you're reading or finding it useful!?
- Posted in:
- ColdFusion
- OOP
No comments
Leave a comment
If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)
