Everything I've done so far in terms of object oriented programming in ColdFusion has been wrong.
I came to this class thinking that I had a decent handle on OOP and needed the class to help me polish it up and tie it together a bit more; I'm sure that I could have been farther from the truth, but not that much farther. I know that an object is supposed to be personified and idealized, which I thought I was trying to do. But, I can see now that I have completely misunderstood the intention of these principles. What I thought was a concept driven by an API is really a concept that should be driven by behaviors. Now, for those of you who always said that you have to ask an object to "Do" something, you're probably siting there saying, "D'uh" and shaking your heads.
This all comes back to Hal Helms' famous question: What does it mean to be an object? When my novice brain tried to make sense of this question, I came at it from an API standpoint: an object has meaning because you can call a particular set of methods on it. While this is true to some degree (perhaps mostly by coincidence), what this actually means is, what behaviors does an object know how to implement.
While the difference between these two definitions might not be obvious at first, I believe that understanding the difference will make the difference when it comes to writing object oriented code versus writing procedural code that resides inside of objects. Now, certainly I don't have a great understanding of this yet, but let's walk though the evolution of one of the problems we discussed today: A task management system.
We literally spent the first half of the day looking at the Task class. To give you a birds eye view of the system, we have projects. Those projects have tasks. Those tasks are assigned to one employee by another employee. A task can either be open or complete. That's just about all there was to it. So the first method on the Task class that we defined was:
What does it mean to be a task? Well, a task should know how to complete itself, right? Ok, cool, but what happens when a task is completed? Is there any work flow that occurs around this event?
At this point, I would suggest that the Task simply turn around and just call the TaskManager class to alert the task completion:
TaskManager.Complete( THIS );
This idea made sense to me because I interpreted "behavior" as "API" in that by having Complete() be part of the Task API it meant that it was part of the Task behavior. The problem with this though is that as the application grows, we end up with massive, procedural "Service" objects and data-driven "Business Objects" that have no true behavior. This is what is referred to as the "Anemic Domain Model."
Ok, so if a Task should not delegate its core responsibilities it means that the Task object has to know how to actually carry out the completion work flow. So, what is that work flow? We decided that this work flow would consist of two steps:
- The "IsComplete" property was turned on.
- The Employee who assigned the task would be alerted that the task was complete.
Setting the IsComplete property is straightforward - it just requires updating an internal variable. But what about sending out the completion alert? Should the Task just send out an email using the CFMail tag?
This question, while seemingly simple is actually quite complex and is, I believe, at the very heart of true object oriented programming.
I don't mean to jump around too much, but before we dive into that question, let's recall a bit of what I said in my last post about the horizontal layers of an application and how only a top-to-bottom relationship should be used:
While seeing the layers articulated at this macro level is good, I think it loses its usefulness at the micro level of an application. What I'm beginning to see is that the more important question to examine is, "Do I ever want to use this object in another application?" While the answer to this question is really what the layers above are trying to provide, I think meditating on the future reuse of the object is easier to understand.
That said, let's now jump back to the problem of having our Task email the completion alert. If the Task uses the CFMail tag directly then the task becomes coupled to the particular application as the CFMail tag requires settings that are, or can be, application specific (think: Port, Username, Password).
Ok, well what if the Task uses some sort of Email Service to send out the alert? This sounds good, but where does the Email Service come from? We can't have the Task itself instantiate the Email Service because this would tightly couple the Task to a particular email service. What if we wanted to change the service class later on? We'd have to go into the Task class and change the type of service that was created. This does not leave the task closed to modification (remember we want it to be closed to modification, open to extension).
So the first solution we came up with for this was to pass the Email Service into the Complete() method:
Task.Complete( MessengerService )
This allows us to create a Task that can delegate the sending of a message to another service without it becoming dependent on the service or on the application. This frees the Task up to be used in other applications.
When it comes to the Task utilizing the message service we first envisioned something like this:
But then someone asked, "What happens if we need to send an SMS text alert, not an email alert?" Or, "What if we need to send a multi-part email for HTML and plain text versions?" This opened up a whole new can of worms and brought us back to the question, What does it mean to be a message? Does a message have an email body? Well, certainly not if it's an SMS text message. And, based on the multi-part email dilemma, it would lead us to think that a single email "body" is no longer sufficient for even emails.
What we begin to realize is that this whole, "What does it mean to be," mantra requires us to define a generic interface for our objects; we can't think about passing email data or SMS data - we have to think about the generic information that all messages need to know? Sender, Recipient, Title, and Data. Once we have this, we can then create an interface for the Message Service that all instances of it will uphold:
With the interface defined, we can now sub-class the Messenger Service to allow different behaviors of delivery - SMS, Email, Fax, or any combination of behaviors (a particular messenger service might send out both an email and an SMS text message).
There's a lot more to this conversation (which spanned about 6 hours) but it's late, I'm tired, and I have to get to sleep so I can get up and do it all over again. Sorry I can't finish the review, but I need to let my brain rest a bit. With all of this though, the real magic is working through these problems as a group. That's something that I'll never be able to properly describe in these posts - you simply have to be here to understand.
To make up for cutting this short, I'll leave you with some of the many classes that we attempted to model:
Want to use code from this post? Check out the license.