Skip to main content
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Steve Withington
Ben Nadel at cf.Objective() 2011 (Minneapolis, MN) with: Steve Withington ( @stevewithington )

Hal Helms On Object Oriented Programming - Day Three

By on
Tags: ,

Today is a bit harder to sum up because we actually started coding today rather than just talking theory and modeling classes. Before I get into that though, I just wanted to talk about my sleep last night; it wasn't good. I had a lot of trouble getting to sleep - I couldn't shut my brain off. It's like it just kept wanting to process all the stuff that I've been learning these last few days. It reminded me of trying to fall asleep before Christmas - it's like you can't keep your eyes closed cause it's so exciting, but at the same time, you know that if you can only fall asleep the morning will come before you know it. This stuff just's exciting!

But I digress - I actually did have some unrest last night because I was not fully satisfied with the domain model that we came up with in class. If you recall from yesterday's post, when a Task is completed (Task.complete()), the use that assigned the task is alerted to the task completion. In order to do this (which I didn't cover yesterday), the Task uses an Messenger service stored in the User to send out the email. The pseudo code looks like this:

function Complete(){
	this.IsComplete( true );

	this.AssignedBy().MessengerService().Send(
		this.AssignedTo(),
		"Task completed",
		"The following task was completed: #this.Description()#"
		);
}

Before I get into my unrest, let me first discuss the logic of the above code. We assumed that a messenger service was defined by the type of message it sent out; one messenger service might send an email, one might sent and SMS text message, or one might send out both. This choice of message transportation felt like a user-preference (ie. the user chooses to be alerted via a particular method). As such, the AssignedBy user is what gives us the messenger service. Once we have this service, we simply execute the Send() method and pass in the From, Subject, and Message data. It is then up to the Messenger Service to format the given message in what ever way is necessary.

This feels like a good delegation of knowledge responsibilities and keeps the coupling between the objects to a minimum; but, what got my knickers in a bunch was the concept of the message format. The way I see it, we are treating this situation as if the "task completed" email is the only one that is getting created in the system. But, what about a "project completed" email or a "new user" email?

For the sake of argument, let's say that we do want to future-proof the application to handle different emails. How could we do that? The first thing that pops into my head is the idea of a Report. In the real world, we might have a report that can be exported as a Word document (DOC), an Excel document (XLS), or maybe as a PDF document (PDF). That's three different types of report, but essentially the same data, so how can we project against that?

One of the things we learned this week is to try to identify the things that change and then encapsulate them. So, when I look at the report scenario, the thing that changes is the output formatting. The thing that doesn't change is the report data. As such, I would create a Report class that holds a standardized report data format. Something like:

Report Class
----------------------------  
- DataXML
----------------------------  
+ Export( ReportFormatter )

Here, we have a class that stores some sort of normalized data. Then, we have an Export() method. In order to keep the Report class generic and extensible, we want to be able to pass in the type of formatting rather than have the formatting logic be hard coded in the report class.

To do this, ReportFormatter would have to be an interface that we could sub-class. Let's say that it looks like this:

ReportFormatter Interface Class
----------------------------
+ FormatReportData( DataXML )

Then, the Export() method could look like this:

function Export( ReportFormatter ){
	return ReportFormatter.Format( this.DataXML );
}

Now granted, I am very new to all of this, and it may very well be completely wrong, but here's my reasoning: by having a Report and a ReportFormatter class, we are completely separating the way in which we store the report data from the way in which we render report data. And, because the ReportFormatter class can be sub-classed, we can easily add new report formats without having to touch the Report class at all. This leaves both the Report and the ReportFormatter classes closed to modification and open to extension.

Or, the even better solution might be to sub-class the Report class directly to have things like an ContactXLSReport class and an ExpensesPDFReport class and then simply call the Export() method with no arguments. This might actually be more appropriate because we can't just blindly format report data can we? The XLS and PDf are "modes of transportation" but there is also the question of layout and "data transformation".

NOTE: As you can see, every answer seems to bring up three more questions. The beauty and magic of the classroom setting is that I can go in tomorrow morning and bring up this Report scenario for general discussion and the chances are good that I'll leave at the end of the day with a solid answer.

If you can follow me so far, let's now jump back to our Task management problem and our Email Service class. At first, it might seem like a great parallel to the Report solution - the Task object holds our "data" (think: Report) and the MessengerService holds our "formatting" (think: ReportFormatter). But, let's think about this concept of being closed to modification and open to extension; imagine that we wanted to add another type of email - a "new user" email that goes out when a user is added to the system. How would we do that?

Well, if you follow the Report model, then all we have to do is use another MessengerService inside of the Task. But there's the problem; where do we get the MessengerService? We get it from the User object (in the AssignedBy property). The rub is that the User has no concept of which Task is in question or that we are even talking about a task - all we're doing is asking it for the messenger - we give it no indication of what type of message needs to be sent.

At some point in this work flow, we need to indicate that we are sending out a "task complete" message and not a "new user" message or a "project complete" message. Now, I don't think this should come from the User object; after all, this is not really the responsibility of the user, it's the responsibility of the application or system - the User just happens to know the contact information.

Do we want the Task to instantiate a message object of some sort to hold the message data? This doesn't really help us since the Task is simply passing the data anyway.

The real problem is that we don't even know what type of message format we have until we pass the data off to the messenger service provided by the User. So, let's say the User has selected an SMS message format - we'd have to make sure that Task somehow translates the message data into an SMS-friendly format (as defined by the business logic) before it passes the data to the messenger service.

To be honest, I really have no idea what the good solution is here. The point of an interface is that we don't have to care what run-time type it is. I think the fact that this process seems so complicated is because there is a flaw in our OO design somewhere.

Regardless of the fact that I don't have a solution at my finger tips, I can see that my way of thinking about problems is totally changing - I am trying to really shift away from thinking about data to thinking about responsibilities and behaviors. Sorry that the third day's round-up is more of a stream of consciousness and less a summary of the day's activities, but I hope that the exploration of my distress can be an accurate reflection of the really great stuff that we're discussing in class.

More exciting stuff tomorrow!

Want to use code from this post? Check out the license.

Reader Comments

153 Comments

Sounds to me like maybe a subscription and event pump system might help here? The task then doesn't have to care who or what is subscribed to "onComplete" messages, if anyone. It just shouts to the world "I'm done!" and then handles requests for additional information from anyone who cares.

function Complete() {
var event = new WorldEvent();
event.setType(EVENT_TASK_COMPLETE);
event.addData("task", this);
event.shout();
}

That also opens you up to having, for example, a shared calendar interface that interacts with MS Exchange to remove the task from a calendar that is shared by everyone but owned by no one.

function SharedCalendar.init() {
listener = new WorldEventListener();
listener.subscribe(EVENT_TASK_COMPLETE, onTaskComplete)
}

function SharedCalendar.onTaskComplete(data) {
var task = data["task"];
removeSharedTask(task.title);
}

15,640 Comments

@Rick,

But, the question is: is it the responsibility of the Task to send out the email? I like the idea of an event listening system that binds to the task-complete event, but I think perhaps we then take responsibility away from the Task.

153 Comments

No, the Task shouldn't care a thing about email. In fact, neither should the User. I'd argue that what you are looking for is an object dedicated to sending out emails when certain things happen to tasks. Call it a "TaskSubscriptionService", if you will. It would act as the go-between for the Tasks and the Users.

Whether that subscription is automatic (everyone associated with the task is automatically mailed), following a business rule (just the managers are emailed), or manual (users have to sign up to specific tasks to be notified) can then be decided by the service. You would then extend the service for each of the notification Formats you wanted, just like you were talking about for the Reports.

55 Comments

It's a funny analogy to the Marketo product I am working on. We have these small "campaigns" in the UI which absolutely have an OO thought process behind them.

Each campaign has a trigger, "What am I listening for" and a flow, "What should I do if that happens". Campaigns are loosely coupled in the sense that one campaign could "remove" someone from another campaign (ejector seat), but they can't "push" someone into a campaign. In other words, the campaigns just listen for the thing they are in charge of and run the process they know how to do.

The thought process we train the users (marketing folks) is this: "Try not to repeat yourself."

I don't think this helps you that much, but I've always thought of OO as having little workers in a vacuum. They wait for something to happen and then they react. They don't know someone might be waiting for them to to do something. Each worker just screams out when they do things and other objects might care.

Alot of what you have been writing sounds like, "one object passes to another". Maybe the thought experiment of making a rule that no object knows that another object exists would help. They just scream out events that happen.

Interestingly, another analogy: This is exactly how networks work. On a LAN, your computer just screams out (all the time) information. It's up to another computer to "respond". There is software that lets you watch the traffic on a hub (not a switch) and its shocking. Every website, every IM, every bit of data streams out in the open.

Anyway, I hope this was maybe a tiny bit helpful.

15,640 Comments

@Rick,

I ran your comments past Mr. Helms and he really liked them. Actually, but the end of Day 4, we did sort of re-examine the Task emailer and we did agree that the emailing should probably be moved out. A lot of this class, however, does involve us going down one path and then discussing it and deciding that it was or was not the right path to go down. I am finding this style of teaching / learning very nice; Hal doesn't really lead the class to much. He sort of just kicks things off and then we all argue it out. When he needs to, Hal steps in and starts to talk about principles and why we should not do something.

Like, occasionally, he just say something like, "I'm not very comfortable with the solution we have - can anyone tell me why?" And then we'll all stop and start throwing around ideas as to why something can be factored out into another object and that sort of stuff. It's very cool.

@Glen,

I think that is a good analogy. And, by thinking in that way, I believe we will be creating objects that are much more independent and therefore much more reusable.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel