Hal Helms On Object Oriented Programming - Day Four

Today was quite intense; we did a little bit of coding in the morning but the class quickly drifted away from the task management system and back into deep conversation. We got a little heated about the layers of an application and which layer should be responsible for what tasks. All the information that we covered was great, but for me, there were few key points.

I think the biggest breakthrough today was the discussion of what it really meant to be an object. Now, I don't mean this in any specific context as in, "What does it mean to be a Contact object?" I mean this purely in the generic sense - what does it mean to be an actual object instance. Brace yourselves cause this is fairly huge and I believe it will rock your world:

An object can only ever exist in a valid state. An object should never be created or allowed to enter a state that is not valid within the domain.

Take a moment and let that really sink in. It took me a good 15-20 minutes to actually let the magnitude of this concept wash over me. It was such a radical departure from any concept of an object that I had ever considered; but, when you stop to think about it, it's almost too obvious (1.21 gigawatts? Great Scott!).

Picture if you will a Human object. Now, imagine that this object has a Head property. Imagine now that we create an instance of "Human" and do not give it a Head property. Now, ask yourself this: is this a Human object instance?

If this were a week ago, I would have surely said "yes" - it was Human object instance, only it would not be valid. But really, this is crazy talk. If you are not sold, then let's stretch the example a bit - is this really any different than saying a Cat object instance is actually a Human object instance only it's not valid because it has no human properties?

Is that example too extreme? What about creating a Hand instance and saying that it's human, only it's not valid because it lacks arms, legs, a torso, and a head?

Are you beginning to understand; an object can only be an object because it has all the required properties that make that object "that object." A Double cannot contain a String value and be considered an invalid instance of Double; a String cannot contain an Array of floats and be considered an invalid instance of String; and, a Human cannot lack a head and be considered an invalid instance of Human.

Back when I was reading the Fundamentals Of Object-Oriented Design In UML by Meilir Page-Jones, I thought it was weird that Page-Jones kept referring to objects as "data types". Every time I saw that in the text, I thought to myself, "No, that's not a data type, that's a class." I thought "data types" referred only to foundation class, but I see now that he was exactly correct; these objects are data types and, as data types, they have properties that will always exist and be true in accordance with what it means to be that particular data type.

Hopefully, you are really starting to see the light here. But, with this revelation of what it truly means to be an object, there is another correlation that we have to accept: objects cannot be validated. Having code that looks like this simply makes no sense:

Object.Validate()

... or:

Service.Validate( Object )

Since the very existence of an object instance implies that it is in a valid state, then there is no additional validation that needs to take place.

Like I said, it took a good twenty minutes for this to really hit me, but when it finally did, it really rocked my world; it fundamentally changes the way I must approach object oriented programming. I used to think that I could just throw data into an object and then check to see if it is valid. But, when I do that, I am violating what it means to be that object. Also, I am thinking of the object as nothing more than a "data container." It is only by upholding a valid state that I can view the object as a true object with an implied meaning.

To ensure that an object is always in a valid state, we must pass in all required properties in the constructor. So, going back to our Human class, the constructor for this would look something like (pseudo code):

function Human( Head, Body, LeftArm, RightArm, LeftLeg, RightLeg ){

.....

}

A valid Human requires a Head, Body, LeftArm, RightArm, LeftLeg, and RightLeg, and as such, all of these objects must be passed into the constructor. A constructor can accept additional, optional arguments - but, there can be no question about the required, composed objects.

Speaking of "composed," this is the first time the difference between Composition and Aggregation ever really meant anything to me. Until now, I had always just thought of these two as pretty much the same thing with some slightly different meanings. But, when you think of an object as composing properties, you realize that on object without those properties are actually not those objects at all.

As huge as this realization is, it does (as most answers in OOP do) create even more questions. Namely, if an object's data must be valid, then who validates the data that goes into the object? This answer was not quite resolved 100% in class, but I am working on some ideas in my head.

But, rather than go into the validation question (which is a large topic), let me finish up some more of the class overview (it's 11:25 PM and I need some sleep ASAP). One of the other big topics we tackled today as a result of the above revelation is what exactly should go in the Controller tier of an MVC (Model-View-Controller) application? It was my belief that the Controller should be as thin as possible - that it should just take data from the request and pass it off so a service layer of some sort.

The problem with this approach comes down to what the job of the Service layer is. If you accept the fact that the Service layer creates our Domain Entities and that our Domain Entities must be created in a valid state, then it's easy to accept the fact that the job of the Service Layer is to ensure the constant integrity of our Domain Model. Therefore, it is must also be true that both a Domain Entity and its paired Service Layer travel together in any application in the same domain.

This connascence between the domain entity and the service layer means that the service layer cannot be application-specific. If this were not a truism, then you could end up having an Application within a domain capable of creating an invalid domain model state. Now, because the service layer is not application-specific, it requires our validation to be in the next layer up - the Controller.

As the most application-specific layer, the Controller must handle our validation and our work flow. This means that it must do more than just marshal requests and hand off data - it must have application knowledge.

... ok, it's almost 12 AM and my brain is at maximum capacity. I have so much more to explore and ideas that I want to talk about, but cannot go into it at this time. This class is simply mind-blowing. I don't know where I'll be at the end of the day tomorrow, but I can tell you that I am already light years beyond where I was 4 days ago.

Reader Comments

The object is called "vehicle". Vehicle requires fuel. Fuel is consumable. Each time the vehicle's "move" method is executed, fuel is consumed in the process, so the fuel amount decreases. You need a way to determine whether or not the vehicle has fuel and moreover you need a way to track it over time and movement so that the driver can plan to refuel. What happens when the vehicle runs out of fuel is that attempts to call the "move" method fail. They may throw an error or they may just result in no movement, but they don't actually move the vehicle in any case.

Here's the twist. The fact that a vehicle moves is the defining characteristic of what it means to be a vehicle. Yet vehicles are frequently in a state in which they are incapable of performing the one thing that defines them. Movement is the reason a vehicle is a data type. But without fuel, they don't move.

So having said all that, if we assume that an object can never be in an invalid state, then we have to accept that although movement is the defining characteristic of a vehicle, being unable to move is then a valid state for a vehicle to be in. Further although you may have vehicle.checkFuel() as a method to return the amount of fuel, there are other valid conditions in which a vehicle may be unable to move. A vehicle may be unable to move if the engine is not turned on or if the vehicle is undergoing maintenance. Those are both valid states for a vehicle even though they may prevent exhibition of the defining characteristic of said vehicle.

Now I would also suppose that, given form validation as an example, because not all data represents a valid property set for a given type of object, validation of user input is a requirement. So somewhere, there absolutely has to be a validate() function that takes some user input and tells the system whether that input is acceptable for the given object. I'll grant that you could argue that it doesn't belong within the object itself (and I won't get into that can o' worms here), but what you're actually doing here is quite different than asking "is this object valid". You're not asking that at all, you're asking "is this data I got from user x okay? Will it work in this context?"

Now although we tend to say that data should never get into the system unless the users have provided a) all the data and b) all the data in the proper format, these can also be problematic from a usability standpoint. Enforcing rigid validation at point of entry can cause problems, because it doesn't allow users to enter partials... and what I've seen often is that even when you allow users to enter partials, they'll often enter data they know for a FACT is bogus (which is a whole other problem) because they've grown so accustomed to being stopped when they try to enter a partial in other systems. If you look at a table with contact information, you'll see where people entered "123 My Street, Gallactica" as the address because they a) didn't have the information yet and b) expected the system to deny them the ability to enter the information they do have.

If you were designing a system to handle user entry however, and you wanted to design it with human factors in mind, you might very well want to allow partials. And in a system that allows partials, again, object.isValid() or perhaps isComplete() or maybe even isCompleteAndValid() may be perfectly valid even given the previous assumption that an object can never be invalid. Because again, you're not asking "is this a valid object". What you're asking is "has the user provided us with data that will work in this context?" Which is in my mind philosophically similar to asking "is this vehicle movable?" Which means that a) someone has fuelled it and b) it's not currently in some other valid state like a maintenance state that would prevent it from moving.

Hi BenIt's been interesting to watch you go through this process of learning CF+OOP. I'm in the same boat and have been doing lots of reading over the past few months and have found your post interesting.A few comments:1. Over the last few days I've got the idea in my head that the "M" in MVC is really a listener layer (which would communicate between the controller and the business layer). So in your message above where you were speaking of the controller doing the validation perhaps this should be delegated to the listener layer. But perhaps this is incorrect because the listener layer is a CFC which should be "properly formed" (or a data type) as per your findings... hmm, I need to think about that so more.2. The other point of interest to me is the justification of how much time it takes to model a true CF+OOP application. Perhaps it's just because we're new so it takes us ages to plan out the app and when you become more experienced you'll be able to build a picture of the classes in your head whilst reading the project brief.3. Can you convince Hal to come and do his workshop in Australia!I'm just rambling really. It's been a long week. I look forward to your final post.CheersMatt

One of the consequences of demanding that objects must always be valid is that at the domain model layer one ends up with a minimal definition of valid. Once you realize how seriously you have to take a class invariant, you really hesitate before defining something as an invariant.

To put it another way: business rules that truly apply without exception are *much* rarer than many people think. It's OK to have application-specific rules that vary from app to app, but coding an invariant into the domain model is the same as saying "In this domain, it would be a fundamental error for this assertion to be false, under any circumstances whatsoever". Pretty strong stuff.

A trivial example: we have a policy that all users must have an email address. You can't register without one. So, this is one of the invariants of my User class, right? Wrong. The truth is, I *want* all users to have email addresses, I *hope* they all have email addresses, I definitely code my registration forms so that they will reject any submission without an email address - but in actual fact one or two users don't have an email address (I won't go into the reasons here). They certainly are users, therefore possesion of an email address can't possibly be a class invariant.

@Ben--thanks for taking the time to share your thoughts and experiences! This has been a really great, thought-provoking series of posts.

@Ike--I think you're confusing the object being valid and the state of a particular instance of the object. A vehicle needs to have the capability to move, but that doesn't mean a particular instance of a vehicle is always in a state in which it *is able* to move. To go back to Ben's example, a Human can't be a Human without having a head, so the appropriate corollary for Vehicle, and let's assume this is a fossil-fuel powered beast ;-), is to say that a Vehicle can't be a Vehicle without having a gas tank. In other words, when you construct the vehicle (again using the Human example as the model here), you could have to include a gas tank in the constructor. If not, it can never hold any gas and is therefore unable to move ever, which is different than a Vehicle having a gas tank and simply being out of gas at a given time. This doesn't mean the Vehicle isn't a valid Vehicle if it's merely out of gas, but one would certainly say "this isn't a proper Vehicle" if there was no way it could *ever* move. I think that's a subtle but important distinction. In other words, it's perfectly valid for a Vehicle to be out of gas at runtime, but we'd probably agree that it isn't valid for a Vehicle to be absent the capability to ever move at runtime.

I'm of the opinion that you should have posted these blog posts with the comments turned off. One, so that you can keep and retain what you're learning without interference / noise of an internet discussion/argument influencing what is currently being taught. Once you're solidified in YOUR feelings of OOP and the whole experience, then open comments to people and chat with them about it. I just fear that your brain is goo and it's easier to poke a stick at it.

It is awesome that you posted it and I'm definitely reading what you're experiencing, so thank you for posting.

The discussions in class have been really great. I totally bought into the objects can't be in an invalid state argument yesterday - although that makes all of my object.validate() code completely wrong.

The big question we need to still fight through is if the object doesn't do the validation than who does. I don't want my service layer or my controller to be fat. And unlike Hal I like my controller to be fairly dumb (which I know is another big argument we had yesterday).

It will be great if we can work through the whole form validation today.

I think these examples touch on the difficulties of properly defining real-world objects ("real-world" meaning both physical objects commonly known to us and programming objects we might have to create at work). After all, a human doesn't have to have arms or legs, right? So maybe a Human shouldn't require those. But if you're in the construction industry (as I am), maybe it should. Or maybe only for some implementations of Human (desk workers, yes; field workers, no). What about prosthetics? Do we need an ILeg interface and HumanLeg and ProstheticLeg objects that implement it?

Like a lot of other programming topics, I don't think there's a single right answer for every instance. You should probably choose the degree of detail that suits your situations best ... unless you have a specific reason to make LeftLeg optional, don't.

Thanks for some good clarification on my comments about the vehicle object.

@Matt Woodward - one of the things I wanted to get at specifically here is that I'm not convinced the "validate()" methods Ben described originally are necessarily a philosophical blunder in terms of OO design. Your clarification actually really highlights what I was getting at, because in the case of designing a system to allow form partials like i described, the meaning of "validate()" would not be asking "does this vehicle have a gas tank?" -- it obviously is capable of storing the user's data from the form (the tank), so that's not a question. Instead the validate() method would be asking "is this vehicle fuelled?" or in the context of form validation, "has the user provided enough of the right information for this form to be pushed forward to the next stage of our workflow?" And this ties back into what Jaime was saying about invariants being rather uncommon. In my mind saying that object.validate() or service.validate(object) is never valid requires an impractical definition of what "validate" means (is this a correct object?) in lieu of a definition that's actually quite practical (is this object prepared to perform its next task?)

So while I do think that "data type" is a good way of describing a class to make the distinction that its properties can't be radically different, my personal feeling on Ben's assertion about "validate" is that it's more a bit of semantic confusion really than a fundamental OO principal. And I think that kind of semantic confusion could tend to lead to some of the cases in which an architect may be over-eager to create an invariant in their class like Jaime mentioned.

Just some friendly stream of consciousness questions... What does valid mean? The correct data type (How is this supposed to work in a dynamically typed system)? The correct length? Max or min values? Some arbitrary business rule? If an object needs to always be in a valid state shouldn't an object be in charge of validating any attempted changes? If validating is moved to a controller doesn't that make that object coupled to the controller?

Thanks so much for posting this series. It's really great to see you growing your own set of ideas about OO and how to design applications, and Hal is definitely great for getting people to think different.

All too often we take the one (vocally advertised) way of doing things as correct. It's great that you're exploring yourself and not just taking all of the "[best] practices" for granted.

@Todd

Comments and discussions are a good thing! :) That said, I have been keeping my opinions intentionally to myself as to let Ben speak his mind and not start the inevitable flame war.

Better to let the ideas sink in and ponder them, even if you don't agree , than to spout off at the mouth about how X person is wrong about OO or Y thing is "bad design". A lot of developers would really learn something if they do that.

@Elliott: My message wasn't meant to be negative. Yes, comment/discussion is a good thing. A classroom setting is already a very precarious thing to begin with, throw too much at student on a controversial topic and it could be a bad thing (for reflection, etc).

I hate to nit-pick at an analogy, but Human's without legs and arms are still human. Humans without heads (decapitated) are still human. Human is a species. We don't stop being a species because we die.

Humans can be in several states including (not limited to) alive and dead. Not to mention adjectives like adult, infant, fetus, etc.

Humans have properties like left-leg. However, a valid human value state for left-left may be "missing".

"Valid" should be a measure of "Is this still a human?" Human means human DNA. You either have it or you don't. Anything with human DNA is a human object. This is why pro-lifers say a fertilized cell is a human being. They are thinking strict object-orientation.

WOW, did I just support a pro-lifer?!? Ok, I need to extend.

A human object is human because of human DNA. Humans have a property called "right-to-live" which is a boolean value. Additionally, they have properties like "can-survive-without-living-in-a-womb". I believe that when a fetus can survive without living in a womb, then the right-to-live property changes to "true".

I think what we really have here is "the" problem when it comes to creating real-world objects and what it means for an object to be personified. We are too emotionally connected to something like a "Human" class, so sorry for bringing that up - it was a poor choice on my part.

The problem is that we humans see the nuances in every day things. For example, we may see a 4-legged table with *only* 3 legs and still consider that 4-legged table. But, that is thanks to the power of the human brain and our ability to extrapolate information.

What if it was actually a 5-legged table missing 2 legs instead of one? What if it was a 3-legged table designed to look like a 4-legged table missing a leg?

As humans, we extrapolate partial sets of data into what we think is the best choice. But in a computer world, with a fixed set of rules, this simply is not a behavior that we can have. And frankly, it's NOT a behavior that we would want. Imagine if a computer program saw a "Leg" object and tried to use it as a "Human" object because it figured they were roughly the same thing? It would be no good - probably throw some sort of exception.

I think that's the point - we don't want computer systems to have to make guesses (unless that is what they are designed to do) - we want them to live in a world where they can believe that certain rules are always true.