Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Denis Burimov
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Denis Burimov

Object Thinking By David West

By
Published in Comments (23)

My software is mostly procedural at heart. I've been told time and time again that Object Oriented Programming (OOP) offers great benefits over procedural programming; and I believe this. But, unfortunately, I find Object Oriented Programming to be a topic that continuously eludes my grasp. I haven't had that "ah-ha" moment of clarity where is starts to make sense. And so, it was with enthusiasm that I started reading, "Object Thinking," by David West.


 
 
 

 
Object Thinking by David West.  
 
 
 

While Object Thinking was a very interesting book, recounting the evolution of software over the last 50 years, I'm not sure that I took very much away from it. From the explanations in the book, I am now more confident than ever that everything I am doing is wrong. But, I don't feel very guided in how to move forward.

Even when the book occasionally dipped down into concrete examples, the examination was so short that it left me with far more questions than answers. Sure, there was talk of "airplanes" and "locations" and "instruments" and "instrument clusters"; but, where did those objects come from? How did they get assembled? How did they get wired together? How many methods does the airplane expose? Does it have to act as an encapsulation barrier for every internal capability? And what of the instrument cluster? Does it just store and serve up instruments? Or, does is compose those instruments and then provide high-level query methods (ex. "what is the current altitude")?

For me, clarity tends to come more from seeing code and how it all comes together.

That said, there were two passages that really struck a chord with me. These passages are not back-to-back; but, I think they are in the same vein:

Object thinking shows little concern about state when objects are discussed specifically because a properly designed object has very little interesting state. What state it might have should be private - behind the encapsulation barrier - except to the extent that the object is willing to make public the fact that a state change occurred.

Most discussion about object state is really about state-based constraints to be imposed on an object. Such constraints are not intrinsic to the object itself; they are an aspect of the situation in which the object finds itself employed. This kind of state modeling is important but not important in advancing our understanding of individual objects. For this reason, discussion of state modeling involving objects will be taken up in Chapter 9.

All other constraints that might be imposed on an object are also reflective of a situation, not of the object per se. Table manners, for example, reflect a set of rules that promote or inhibit intrinsic human eating behavior - fingers and communal bowl in an Ethiopian ethnic restaurant; a plethora of special-purpose utensils and prescribed behaviors at a formal state dinner. It's a mistake to attempt to incorporate this kind of variation into an individual object's specification.

... and then later on when discussing the constraints involved in a "home load application,"

... the home price must be less than or equal to the median price of homes in the neighborhood. This rule is not a domain rule; it is an application rule. It should not become a basis for defining the home, neighborhood, or price (actually money) objects. Instead, it shows a relationship that exists only in one or more applications (contexts), perhaps the "collect and validate loan application data" application.

I found these two passages to be extremely interesting because they touch upon two topics that I struggle with: the size of objects and the location of "business constraints." My stuff needs to be smaller and my logic needs to be more distributed. I feel that; I just don't know how to do it yet.

I'm told that this book has a "love it or hate it" following. I didn't hate it; but I definitely didn't love it. I did get a few things out of it; but, I feel like I would have gotten the same value even if the book was about a quarter the original length. What I really need to do is start playing with some more code and get thinking about how to better split my programming along the "natural seams" in the problem domain.

Reader Comments

1 Comments

Coming from a big brain like yours, I don't feel quite as stupid that I haven't had my OO "a-ha moment" either, even after 13 years of programming and building some pretty complex stuff. I always read your blog entries when you approach the topic because I'm hoping you're going to help me get there one of these days. Thanks for your thoughts and book review. I guess me and my little brain will keep waiting...

21 Comments

I think I'm in the same boat where I read a lot about OO and how to design objects themselves with a bunch of generic examples, but I run into the same issues around business logic and constraints and how that fits in with those object models. I'm glad I'm not the only one in that boat and appreciate your sharing.

15,848 Comments

@Jeff, @Justin,

Glad I'm not alone in this boat :) What's incredibly frustrating is that on its own, an object "feels" like a really easy thing to think about. But then, when they start interacting, it just gets really ambiguous, for me.

Take, as an example, even something super simple like a ToDo list. A ToDo list has tasks. That seems really simple. But, what if I throw in the constraint that no Task can have the same title. Meaning, you can't have two tasks that both say, "Take out the trash."

Where does that tiny little constraint lie? Clearly not in the Task itself, since the Task has no concept of other tasks. It feels like it should go in the TaskList. Since the tasks are unique to the list, I guess then the TaskList would wrangle the adding / editing / deleting of task items. I think this is what an "Aggregate Root" is in DDD.

But, that would entail that I instantiate every Task in a list every time I wanted to add / edit a task item, since I need to enforce that constraint.

And, what if I could add "notes" to tasks? Would I then need to instantiate every note and every task when I instantiate the list? Or, would the Note be completely unrelated entity.

... see, that's what makes me sad. Here's a concept that is about as simple as simple can be, and yet, even at that level, I feel like I have no true understanding of a good approach.

15,848 Comments

@Me,

I don't think the language is so much of an issue. Each language has its own nuances (ex. Prototypal inheritance vs. Classical inheritance); but, at the end of the day, I think that any language that supports classes or modularization will lend to the same principles.

20 Comments

@Ben,

You're exactly right - you'd ask the TaskList to instantiate a task. Not sure why that's a problem - if you don't have a list of tasks, then title uniqueness can't be a problem, right? Conversely, if you care about a constraint, you care about it in a particular context. So you instantiate that context as an object and voila! In the case of uniqueness, the context is going to be membership of some collection, even if the collection is just the list of all objects.

What you then get for free is that you get to say things like "OK, task titles don't have to be unique in the universe, they just have to be unique for each task assignee". You move the constraint from TaskList to PersonalTaskList and everything still works. This is important because IMHO real, permanent, constraints on object models tend to be fairly few in number. Constraints tend to be situational and context-dependant.

11 Comments

Sounds like a conference like NCDevCon needs a hands on 2 hour session on OOP...So, time to hit Matt Gifford up and get him there to go through his book in detail, maybe you and many of us would have the ah-ha moment.

15,848 Comments

@Jaime,

In this kind of a context, who would handle the actual creation of the Task item? I can see it being one of two ways - the task is created outside of the TaskList, but the List is used for association:

task = new Task( "Buy flowers" );
 
// NOTE: May raise unique-value exception.
taskList.addTask( task );
 
save( taskList );

... or, the actual task creation is encapsulated within the TaskList itself:

// NOTE: May raise unique-value exception.
task = taskList.addTask( "Buy Flowers" );
 
save( taskList );

The former feels more natural for me because it more closely mirrors the procedural programming that I am used to; that is, I have the control over who gets created, when, and what I do with those various objects.

The latter feels more encapsulated, which I think will prevent me from doing something stupid like trying to update a Task outside of the list context:

task = taskList.getNextTask();
 
// NOTE: Nothing here to ensure the uniqueness.
task.setTitle( "Take out the trash" );

... if, however, I had to perform all actions through the list, then I would have protection:

taskList.changeTaskTitle( 3, "Take out the trash" );

Here, all changes to the internal data have to go through the task list. Which is powerful; but, this also feels very awkward to me. I don't even know what this method signature would look like. In my pseudo-code, I passed in an ID (3), but I am not sure where I would have gotten that from.

29 Comments

@Ben,
One of the things that really helped me get away from writing procedural code was taking an O.O.P. class and being required to write functions no longer than an arbitrarily small number of lines. It can help you see what belongs together, what doesn't belong together, and what can be generalized when you're forced to write like that.

It doesn't necessarily get you all the way to OOP. It gives you a start because it helps get you thinking about how to generalize your code. You start realizing, "I've written something like this already. How do I tweak that code to work here, too?" That's the kind of thinking you want to start with.

What you've exposed of the book is the classic "black box" style of OOP. As with the instrumentation in the plane, you can't simply set the airspeed; the airspeed is read-only. You can affect it by moving the throttle, but there are external forces like turbulence and tailwinds that can affect it, too.

With a "black box" mentality, your object handles all of the logic. One object that I'd written handled attendance-based billing for daycare centers. There was a huge amount of processing within the object, but the only information you provided was, "Which child?" "Which set of rules?" and, "What date range?" The object retrieved the schedule and attendance information for that child during the specified date range, set it against the rules, calculated the bill amount, and flagged any errors or warnings.

I used another example of the "black box" mentality when writing an old-fashioned adventure game (think Zelda of the 1980s). The object handled movement and event logic. All you did was tell it what your character was doing, e.g., "Move north," or "Dig." The object knew where your character was and what the map around him looked like. It handled all the logic: "Can he move there?" "Did he find something?" "Did something find him?" It would return all this information in a structure.

To get to this point, you want to think about what your object can automate, what your object cannot automate, what an external entity must do, and what an external entity should not do. With your tasklist example, you have a collection of tasks: the collection is an object and each task is an object. You want to make sure that your collection of tasks doesn't have any duplicate tasks. The external entity, i.e., the user, should be able to change the name of a task, but the tasklist should ensure it is still unique.

How do you accomplish this? There are a few ways. Certainly, you start by encapsulating the task name and preventing the user from changing that directly. This means that the task would have a setter regardless of how you do it. Of course, the setter also prevents any processing from happening if the old name and the new name are the same; you can run into trouble otherwise.

If you want to make it possible to change the name of the task through a method of the tasklist, there are three different ways to identify the task. You can provide the index, the existing title, or a pointer to the task object itself. You can get any of these three things through other methods of the tasklist.

If you want to make it possible to change the name of the task through a method of the task itself, the task should have a pointer to the encapsulating tasklist. The task checks to see if it's part of a list when you call the setter and, if so, checks in with the list (passing "this" as identification) before setting the task name.

The thing is, the task handles setting the name and the tasklist handles ensuring uniqueness. The task has to realize that it could be part of a tasklist, so you incorporate logic to deal with that. Since the task handles setting the name, even the tasklist has to use that setter to set the name.

var Task = function(taskName) {
var _complete = false;
var _list = null;
var _name = taskName;
this.getTaskName = function() {
	return _name;
};
this.setTaskName = function(newTaskName) {
	if (newTaskName != _name) {
	var okToChange = true;
	if (_list != null) {
		okToChange = (_list.getTaskByName(newTaskName) == null);
	}
	if (okToChange) {
		_name = newTaskName;
	}
	}
	return this;
};
this.addToList = function(taskList) {
	_list = taskList;
	return this;
};
this.dropFromList = function() {
	_list = null;
	return this;
};
this.completeTask = function() {
	_complete = true;
};
this.isComplete = function() {
	return _complete;
};
};
var TaskList = function() {
var _tasks = new Array();
this.getTaskByName = function(taskName) {
	var taskFound = null;
	for (var i = 0; (i < _tasks.length) && (taskFound == null); ++i) {
	if (_tasks[i].getTaskName() == taskName) {
		taskFound = _tasks[i];
	}
	}
	return taskFound;
};
this.getTaskByIndex = function(taskIndex) {
	var taskFound = null;
	if (taskIndex < _tasks.length) {
	taskFound = _tasks[taskIndex];
	}
	return taskFound;
}
this.addTask = function(taskToAdd) {
	var newTask = null;
	if (typeof taskToAdd == "string") {
	if (this.getTaskByName(taskToAdd) == null) {
		newTask = new Task(taskToAdd);
	}
	} else if (typeof taskToAdd == "function") {
	if (this.getTaskByName(taskToAdd.getTaskName()) == null) {
		newTask = taskToAdd;
	}
	}
	if (newTask != null) {
	_tasks[_tasks.length] = newTask;
	newTask.addToList(this);
	}
	return this;
};
... // I think you get the idea at this point.
};
15,848 Comments

@Paul,

I really appreciate the generous feedback. I like what you're doing here. I need to let it sink in a bit. On the one hand, something about going through the Task item and then dispatching to the List allows all the consistency to work. But, on the other hand, something about it just feels odd. But, that could be my inexperience with such an approach.

Thank you again for the example - really helping me think through this.

1 Comments

This is a really great discussion. I would make one suggested change to the previous example by which the TaskList uses setTaskName:

In Task:

this.setTaskName = function(newTaskName) {
	if (newTaskName != _name) {
		var okToChange = true;
		if (_list.isValidTaskName(newTaskName)) {
			_name = newTaskName;
		}
	}
	return this;
}

In TaskList:

this.isValidTaskName = function(newTaskName) {
	return this.getTaskByName(newTaskName) == null;
}

This takes the business logic of "uniqueness" out of the task itself and places it in the hands of the TaskList that it is a part of. This allows any subclass of TaskList to determine its own constraints (e.g. names must begin with a capital letter, names cannot be longer than 16 characters, etc.) and makes both Tasks and TaskLists more flexible.

James

15,848 Comments

@James,

Also a very interesting idea. This is the kind of stuff that doesn't occur to me very readily because I have very little experience with objects actually having to collaborate in order to accomplish a given task (no pun intended). Things like this, and the concept of "double dispatch" feels powerful, but at the same time, I have a mental block about things needing to collaborate. A big part of me is always like, "yeah, but what IF someone forgets to write it together that way?"

Now, I know that may sound crazy, because the "someone" in this situation is Me :) But, it just feels weird :)

29 Comments

@James,
That's a really good catch. You're right: the task shouldn't care what it takes to fit in with the rest of the list. It should leave that up to the list. It should simply say, "Hey, List! Is this change ok with you?" and change the name if the response allows it.

@Ben,
It really comes down to a sound design. As I'm sure you're aware, a huge part of the development process is actually consumed, not by the original development, but by the maintenance of that code and the expansion of it. This is why it is really critical to spend time thinking about the delegation of work and outlining of interactions between objects.

It sounds like a lot of work, for sure, and it can be. The reward is actually somewhat bittersweet. If you design your object library really well and the code is really robust, you find that it is really easy to expand it to do new things. On the other hand, if the code is that well built, if you have to work on the internals and you didn't document the hell out of it, it may take you a while to understand what all you were doing in there.

15,848 Comments

Jaime Metcher and myself had continued the conversation a bit, offline. But, I wanted to post it here since I think it adds value.

Jaime Metcher:

Re creation - definitely the latter, i.e. task = taskList.addTask( "Buy Flowers" ) (although I usually name the method "create"). There's been a fair bit written in the OO literature about the importance of encapsulating object creation. Naked constructors are definitely a code smell to me.

Re setters: some interesting discussion. There are a lot more trade-offs here, so the answer isn't as clear to me. However, I can say a couple of things:

1. There's a tendency to expose way too many setters. A lot of fields don't need (and shouldn't have) public setters. Instead they should be wrapped up in public API methods. It's the API method that will guarantee validity and consistency. It's not reasonable to expect validity and consistency from atomic field operations.

2. In practice, I've never found a need to change the unique name of an object once assigned, so I don't have a setter at all for these fields. In a modeling sense I wonder whether the notion of a mutable yet unique value makes sense at all. No doubt someone will be able to point out a use case where this is needed.

BTW, in the interests of full disclosure I should say that my comments here come from my Java experience. I moved this kind of code to Java at around CF7 because at the time CF was too slow to really support a collection-based approach. Maybe that's changed with the recent versions, but I couldn't say. So I'm treating this as an OO design discussion, acknowledging that implementation practicalities might get in the way.

Ben Nadel:

I agree that the task = list.createTaks( "" ) approach feels a bit better. Plus, it allows the Task constructor to take items (such as a List reference) in its init() method that might otherwise be awkward to have on hand. Or, might require setter-based injection (I tend to prefer constructor based injection when possible because I think it makes the object feel "safer").

I think CF is getting much faster. I still think you wouldn't want to create large numbers of components at any one time; but, from what I've seen, life is much better in the last 2-3 releases of the language.

I agree with you about the setters - I would also try to limit then, I think. But, as to the unique naming, one thing I can think of that I come across often is a user having to have a unique email within a system; and, being able to change that email over time. Of course, in that case, the *cost* of a user collection is probably far too large and the unique constraint would likely need to be delegated to some domain service that sees the application as the "context" as opposed to something small and focused like a List.

Yar! Too much to think about. I wish this felt more natural.

Jaime Metcher:

Fair point re the email address. Although - do you really just go ahead and call user.setEmail()? Or do you send a confirmation to the old email address, then ask for a password, then confirm the new email address etc.? You get my point - this is a much more heavyweight process than calling a setter. Where the uniqueness check sits in all of this is almost irrelevant - it can even be done in batch mode. I just get the feeling that anytime you do need to change a "unique" value it's often more like this than just a simple setter.

You know, the weird thing is - and not saying this with any implication about how you should or should not think about it - this stuff does feel entirely natural to me and pretty much always has, ever since I first picked up Smalltalk in 1984. Hell, I get it wrong as much or more than anyone else, but I feel right at home the whole time I'm coding up my next OO bug :) Is it just a matter of picking a coding style that fits one's innate thought habits? I wish I knew.

15,848 Comments

@Jamie,

True - unique email addresses do usually entail some more process than just calling a setter. But, when it comes down to actually persisting it, I would probably do something like:

lock (name=email) {
 
	user = userRepository.getById( id );
	isEmailInUse = userRepository.getByEmail( email );
 
	if ( isEmailInUse ) throw( .... );
 
	user.setEmail( email );
	save( user );
 
}

... so the email-uniqueness constraint would be enforced at the "domain service" level, rather than in a "user collection," simply because there's far too many users to load in order to check that against a programmatic collection. In this case, I just let the Locking / Database do their thing.

It gives me hope that it feels so natural for you - hopefully I'll get there!

15,848 Comments

@Paul,

... and, if you didn't design it well, from what I hear, you get all of the *overhead* off OOP and none of the benefits. I think for me, the biggest hurdle is just thinking about multiple objects coordinate. This conversation about the TaskList / TaskItem has been hugely beneficial. It's just a small, simple context; but it sheds light on how different my "procedural" mind may really see things.

I'd like to take this example and work it into a single-page JavaScript demo, just so I can really see / understand the full lifecycle of the objects. It seems small enough in scope to be doable, yet complex enough to show off coordination and cooperation of the objects.

29 Comments

@Ben,
If you didn't design it well, you have all the benefits of OOP, but it becomes more difficult to extend the functionality. Any immature design (and I've written my share) will eventually reach a point where it becomes difficult to extend the functionality.

Take a look at what James pointed out above. If we had proceeded with the design I'd written out, we would have encountered difficulty if the list wanted a different way to identify or validate the task. Even the fix that James provided potentially limits the list by only providing the new name instead of a potentially altered task.

What's my own experience? I design and write up an object library with the resources available at the time, including: understanding of the intent; time to identify future needs; knowledge of the language and its capabilities; and time to implement. As I work with my object library and extend it, I will encounter an obstacle. At that point, I can either spend the time to rework the object library or simply shoehorn it into doing what I need it to do and remember that when I want to rework the whole library next time.

Each time I rework the library, the design matures and I bask in the glory of having written a better version. I eventually run into the obstacle with that version and go through the process again.

19 Comments

@Ben,

As the one who recommended the book to you, I feel a little bad you didn't find it as revelatory as I did. But I'm glad you got something out of it, and the discussion here is great.

I would say that your critiques, especially about the length, and wanting to see more examples worked through, are valid. My overall feeling, though, was that the insights were so deep that they made up for the book's flaws many times over.

Also, glad you enjoyed Carlo's blog. The Fowler book Patterns of Enterprise Application Architecture is pretty much entirely example based, so you may enjoy that one more. Though I'd warn that it too is a bit of a slog, despite being very good.

For a much more accessible, but still very good non-slog, also completely example based, check out:

Practical Object-Oriented Design in Ruby: An Agile Primer by Sandi Metz

I'd be pretty confident that with your programming experience, even if you don't know any Ruby it wouldn't matter at all, and you'd still be able to follow all the examples. This book is sort of the mirror image of the West book -- all practical advice and examples with very little underlying philosophy and history. So having got the history under your belt already, it might make a good complement.

15,848 Comments

@Paul,

I definitely have a mental hurdle when it comes to discovering better design over time. My brain has this insane urge to just understand how you do something right the first time :) But, everyone agrees with you - it's not about doing it right the first time, it's about getting it right over time, and embracing the refactor and more information comes to light.

This compliments the whole test-driven-development stuff quite nicely!

15,848 Comments

@Jonah,

Please, don't feel bad at all. I appreciate all that you have contributed to the conversation. And, the more points of view I get, the more confident I am that I will some day, some how understand all this stuff :D

I actually read the Sandi Metz book a few months ago. I now know all there is to know about calculating gear ratios and preparing for bicycle trips :) It was a really interesting book. I haven't reviewed it yet because I needed to let it sink in. I think I read through it a bit fast (I tend to get over-eager with these types of books, looking for the "answer"). I did like a lot of her double-dispatch stuff. And, all the TDD stuff as well.

52 Comments

I've long tried to evaluate the efficacy of development theorem vs. practical application, and I deal mostly in OO vs. procedural.

Not to wax capitalist, but the "right way" is the one that doesn't produce errors, makes clients happy, and gets one paid.

I deal primarily in a seriously undisciplined OOP product, and I would echo what someone else said...there comes a point where the most purposefully designed piece of object oriented programming becomes the victim of its own success.

Developing a web-based sales order process for an ERP system, for instance, one designed principally around batched transactions, and I discover the efficacy of object oriented programming, but then its ultimate shortcomings.

You have your class of customers.
You have your class of addresses.
You possible have a class of sales people.
You have either item pricing or customer-based pricing.

All of these things require attending to business logic that would take place in the bricks and mortar implementation of the ERP system, but you don't have available. I find objects, with properties and constructors that afford that logic, to be extremely helpful.

Combine this with our consistent issue as web developers, the fact that the world of HTTP requests and responses is for all intents and purposes, mostly stateless itself. Almost all of our platforms have mechanisms for introducing statefulness, but there is a certain amount of event modeling or conditioning we still have to place around what we are trying to achieve. Say I have this complex web ordering system, and I merely need to change the shipping method on several line items. For the web in particular, whatever model we use, we either have to isolate our function and play within the DOM, or find a way to target that in a single request and still expect the entire page state in the response.

In an object oriented land, the more a client might inject complexity into those individual pieces ("where is my drop ship option? where are my trade discounts?") the more we have to extend our individual objects to contain them. Which almost always necessitates refactoring the consumptive end. And then, because we're juggling page load times with the complexity of what we're doing, we still have to piecemeal around the transactional reality of our medium.

I try to envision what Albert Camus would write about what amount to doctrinal programming concepts, and realize he may well already have:

"So long as the mind keeps silent in the motionless world of its hopes, everything is reflected and arranged in the unity of nostalgia. but with its first move this world cracks and tumbles: an infinite number of shimmering fragments is offered to the understanding. We must despair of ever reconstructing the familiar, calm surface which would give us peace of heart."

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