Hal Helms On Object Oriented Programming - Day Two
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:
- Task.Complete()
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:
function Complete(){
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:
- Interface
- Application
- Domain
- Foundation
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:
MessageService.Exexcute(
From,
To,
Subject,
EmailBody
)
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:
MessageService.Execute(
From,
To,
Subject,
Data
)
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.
Reader Comments
Thanks for sharing, Ben. This opens up a whole new way of thinking for me in terms of OOP. I'm looking forward to the rest of the week ;).
@Francois,
I am more than happy to share. This stuff is so new and exciting for me, I can't help but put it out there. I only hope that I can really capture what it is we are doing in the class. If anything comes across murky, it's probably because I don't full understand it yet. Even last night, writing this, I came up with new questions that I need to ask immediately when I get to class :)
These posts from Florida are pretty thrilling to read, Ben. Keep up the good work, and enjoy that weather! It's getting colder in NY, so prepare yerself for a chilly return.
Hal did the same thing to a class I was in a year or so ago, albeit with a less practical example.
As was planned, I'm sure, it quickly exploded into a *lot* of modeling for what we thought was a simple task, with Hal saying over and over again "What does it mean to be that", and "Objects do things, they don't just know things".
I actually wanted to come to this class- talked to Hal about it shortly after he posted on ColdFusionCommunity.org. Sadly, life interfered. Glad to see you're taking notes. :)
@Dave,
Yeah, it will be weird getting back there. The water down here is apparently 78 degrees .... in the OCEAN! We'll get together when i get back.
@Matt,
Yeah, its been fun and exhausting. Sorry if I didn't respond to your email (you had mentioned you tried to come down here). With the class, I have like no time to check email or read blogs. It would have been awesome to meet you in person. Definitely at the next class ;)
Glad to see you're getting a lot out of this class, Ben.
I would agree that the question ""Do I ever want to use this object in another application?" is an important one in determining how much thought and effort you need to put into an object: designing an object (or even a chunk of procedural code) to be reusable in a wide variety of situations takes a lot more work than designing one for a particular, fairly app-specific task.
It really seems like data centric applications which are considered small applications or projects should just stick with an MVC framework with procedural code. How can a small handful of people working on multiple projects and applications have the time and resources unless they were just extending a pre-built generic OOP API for coldfusion. Currently, unless I'm mistaken, no such beast exists.
What is considered small? I have no idea.
I'm really enjoying your posts. They are certainly light shedding and I'm sure they are helping your mind recap.,
@Brian,
Absolutely! Thinking about how you can encapsulate all the stuff that changes and finding ways to make an object totally generic is quite hard. Still wrapping my head around it.
@Justin,
I think you make a good point. There is definitely a good deal over overhead to OO - primarily in the design of the classes. Not all application need to make use of this. It has a tremendous amount of power in it, but if you application does not require that kind of future maintainability, then I think you can do just about anything that gets it to work (cleanly).
So, we know that good solid OO is necessary for enterprise team-development apps maintainability and development.
The question is...
Is code generated anemic domain pseudo-OO good for tiny team apps or smaller scope apps? Let's say that you plan on the app being in existence for 15 years with perhaps 300-500 hours of "upgrades" per year (including design elements). At most the team will always be one or two people.
For instance, this would be an application that a small scale cabin rental company in the Poconos uses. The "big" app would be something a 5 star resort with multiple locations would use.
OR do we stick with an MVC framework and very CLEAN code that uses ColdFusion's built in mechanisms for performance, ease of development, and data management.
So, "anemic domain" OO CF code or procedural code for the small time cabin operation? (forget about the potential to just purchase a pre-fab reservation system).
@Justin,
I will try to address this kind of question at the end of the week. I have had a number of comments and direct comments with the same sentiment. To be continued...