Context is King
Now, as promised, the solution to the Customer/Invoice mystery. Customers, we said, have Invoices. And we now know that this relationship has nothing to do with databases and queries. Instead, a Customer object holds an array of Invoice objects:
<cfcomponent displayname="Customer">
<cfset variables.invoices = ArrayNew(1) />
<cffunction name="addInvoice" access="public" returntype="void" output="false">
<cfargument name="invoice" type="Invoice" required="true" />
<cfset ArrayAppend(variables.invoices, arguments.invoice) />
</cffunction>
<cffunction name="getInvoices" access="public" returntype="array" output="false">
<cfreturn variables.invoices />
</cffunction>
</cfcomponent>
When you need to add an invoice to a Customer, you call the "addInvoice" method, passing an Invoice object as an argument. So, there it is. I'm just glad I was able to help you solve the mystery of the relationship between Customers and Invoices.
Or did I? Maybe Customers don't know about their invoices at all; perhaps the Invoice object should know about its Customer. Like this:
<cfcomponent displayname="Invoice">
<cfset variables.owner = "null" />
<cffunction name="getOwner" access="public" returntype="Customer" output="false">
<cfreturn variables.owner />
</cffunction>
</cfcomponent>
That makes sense. Forget what I said about Customers having Invoices; Invoices should have a Customer. There. I'm just glad I was able to help you solve the mystery of the relationship between Customers and Invoices.
Or did I? The way I have it, no one can tell me all the outstanding invoices for a customer. So, maybe we should have Customers holding their Invoices AND the Invoice holding its Customer?
But we know a two-way relationship like this is forbidden in the relational theory, so we have to rule that out -- although it sure would be helpful at times. So, we move on to a third solution: we create an AccountsReceivable object that will hold a reference to Invoice objects and each Invoice holds a reference to a Customer!
<cfcomponent displayname="AccountsReceivable">
<cfset variables.invoices = ArrayNew(1) />
<cffunction name="addInvoice" access="public" returntype="void" output="false">
<cfargument name="invoice" type="Invoice" required="true" />
<cfset ArrayAppend(variables.invoices, arguments.invoice) />
</cffunction>
<cffunction name="getInvoicesForCustomer" access="public" returntype="array" output="false">
<cfargument name="customer" type="Customer" required="true" />
<cfset var customerInvoices = ArrayNew(1) />
<cfloop from="1" to="#ArrayLen(variables.invoices)#" index="i">
<cfif variables.invoices
.getObjectId() EQ arguments.customer.getObjectId()>
<cfset ArrayAppend(customerInvoices, variables.invoices />
</cfif>
</cfloop>
</cffunction>
</cfcomponent>
We have three solutions, but which -- if any of them -- is right? In certain forums, you ask this question at your own risk of being flamed by self-righteous bigots for whom there is only one right way (that just happens to be their way). I find it interesting that the more someone deeply understands something, the more they realize that there is seldom one right way. Instead, they're looking for a way that works within the given context. And context is exactly what we don't have here. It's too simplistic to say that "a Customer has Invoices". This is a discussion that must be had in the broader context of what we're trying to accomplish and who we're trying to accomplish it for.
Let me say one more word about this, because I see too often a type of paralysis that afflicts people coming into the OO world. The affliction occurs because people are worried that they'll violate some stricture. Maybe they'll accidentally unencapsulate polymorphic cohesions. Or something. But the only way to get good at stuff is to do it.
Now, as a teacher and trainer, I (of course!) am not arguing against education. But education can only prepare us. Each of us must, if we are to master something, internalize it. Rules can help us avoid gross mistakes, but they can never provide the kind of elegant solution that we long for. The only way to gain experience is to attempt something and that usually means making some mistakes. That's OK -- as long as they're not fatal ones. I'd much rather someone make a honest mistake than slavishly follow rules or dictates that they don't understand. In short, being "right" is a losing proposition.
Well, that was more than one word on the subject, but there it is.
You might wonder which solution I would prefer? If I had to make a decision without knowing the context, I'd probably opt for going the AccountsReceivable route. It seems to offer the most flexibility. As for the "invoice-holds-the-customer-and-customer-holds-invoices" solution, it might be anathema in the database world, but there's absolutely nothing wrong with two objects holding references to the other. Note that all the solutions involve objects working with other objects -- there's not a primary or foreign key in the bunch.
The problem for many ColdFusion programmers is in trying to integrate OO with the work they presently do. They know how to do procedural design, but how does all this object stuff fit in? Who's creating these objects? Who's calling their methods? Is this in any way compatible with frameworks like Fusebox and Mach-II?
That will be the subject of the next ON ("Occasional Newsletter"): understanding tiered architectures. Don't worry: it's not nearly as formidable as it sounds.
In the meantime, if you or your company are interested in me consulting/mentoring/training, please call me at 678.656.6909 or email me at hal<@>halhelms.com.
As for open-enrollment classes...
On January 24-27, I'm holding a FastTrack to Fusebox 4.1 here in Atlanta. I usually only hold these 3 times a year, so if you want to master Fusebox, make plans for Atlanta!
On February 21-25, my Java for ColdFusion Programmers class goes to Las Vegas. With cheap flights and cheap room rates, Las Vegas is a tough venue to beat (in more ways than one, I suppose!)
On February 28 - March 4, I'm holding a 5-day "Designing and Developing OO Applications with CFCs." If you want to learn how to make OO work for your ColdFusion applications, this is your class.< /p>
On March 7 - March 11, Ben Edwards and I do our FastTrack to Mach-II class, also in Vegas.
To unsubscribe/change profile: click here
To subscribe: click here
--------------------------------------------------------------------------------
Email list management powered by http://MailerMailer.com