Information Management
When we talk about object-oriented programming, one of the most important concepts that pops up immediately is encapsulation. I've given a lot of thoughts to it and discovered a very interesting approach.
First, when we talk about encapsulation, we really talk about information hiding. We want to hide as much knowledge as possible and bury it deep within an object's control, so that the object remains free of changing it's internal structure.
Knowledge
So one of the hardest part of object-oriented program is to manage the knowledge objects have from one another. There's many types of knowledge, or more often called connascence :
You can know the messages an object can respond to, or in other words, the methods it offers
For each method, you can know the parameters, their type, and the order of their parameters, as long as their output value
For objects with protocol, you may know that you have to invoke methods in a certain order
When you create an object, you have to know its dependencies and the order of its dependencies
Sometimes you even know how an object behave internally, and code in a way that makes assumptions about how other objects work
And that, my friend, is the biggest problem of any software system that exist, no matter the paradigm.
Every piece of knowledge reduces the extent to which other objects can change.
Relationships
Let's take some code.
For context, we're looking at code for a sample carshare application for which a user can plan a trip and allow other users to join him, sharing the price of the trip.
What's interesting is the relationship between TripPlanner
and PriceCalculator
. The analysis yields very interesting facts about the knowledge and the direction of that knowledge.
First, TripPlanner
depends on PriceCalculator
and not the other way around. Looking at the code, it seems clear that PriceCalculator
doesn't care the least about TripPlanner
, so changes in TripPlanner
will not affect PriceCalculator
. Quite the opposite, since TripPlanner depends on PriceCalculator, any change in PriceCalculator may impact TripPlanner.
Why is it problematic ? Because PriceCalculator, which doesn't care about TripPlanner, loses its freedom to change from the moment someone depends on it. Any change can potentially disturb any of the dependants.
That's a fact : objects that depend on you may impede your freedom to change.
So in order to maintain your freedom, you must reduce that knowledge as much as possible. You cannot completely remove it, otherwise there wouldn't be any relationship. A relationship imply some knowledge sharing.
And here's the first rule : object-orientation is about controlling and reducing the knowledge between citizens of a system.
By citizen, we mean objects.
There's another way to put it : object-orientation is about balancing dependencies, as said by Robert C. Martin.
Because by controlling that knowledge, you control the degree by which an object can evolve.
So much said for so little, right ?
Public Contract
Look at this very innocent snippet of code.
Great.
What happens when a third argument is added to that method ? When the order of the arguments change ? When type of one argument changes ?
Suddenly, you have to change all the code that depends on that object.
So, as the PriceCalculator
object, you wish to be free to change the way you work and you may sometimes need more (or less) informations to do your job. Unfortunately, doing so will necessarily have consequences on those objects that depend on you.
How do we mitigate that issue ? It turns out we can't.
That method is public knowledge now. Any object who wants to work with PriceCalculator
knows about that method, its signature and its arguments. PriceCalculator
advertised its services, now it must stay true to its word.
Hence, you want to minimize that surface and carefully think about it. Because that public interface doesn't belong to you, it belongs to those that depends on you. You have the duty to maintain it and not to break the contract, or other objects will suffer.
An other view of the PriceCalculator, one that really makes it explicit about the contract it is maintaining, is the following.
This makes it plain obvious that the interface is imposed onto the PriceCalculator
object, and it must obey it.
And that's what design is really about : thinking about how objects of a system communicate with one another and what's the responsibility of each object related to the others. In other words, it's thinking about flows of communications and dependencies.
When you think about your objects, you must carefully think about its public contract. This contract is composed of :
Its public methods
The return values of these methods
The argument of these methods
But also :
The protocol of the object, or in other words, how methods should be invoked
The preconditions, the state of object and of the arguments before a method is invoked
The postconditions, what's the new state of the object after the method is invoked
The invariants, the rules the method will obey at all costs
That's already a lot of information, and yet this is the very minimum an object can tell about itself for communication to happen. Communication, by its nature, imply all these aspects of the public contract of an object.
Note, once again, that all these requirements are imposed by the depending object, not by the object being depended upon.
Why am I insisting ?
Because that's really the keystone of Pragmatic Objects : objects are always developed for the needs of an existing object. And the first object in this chain is driven by the needs of the end-user of the system.
Information
There's many kind of objects, yet we can separate at least two big categories of objects : stateful objects and stateless objects.
Stateless objects are most often services and controllers. They're like an administrative counter : they take in requests and give responses. They communicate with other objects that are often stateless themselves in order to carry their duty. They're coordinators.
Stateful objects are all other type of objects, usually domain objects like entities and value objects. They're the gist of your application. This is where stuff happens.
Turns out, these objects have state, and they do for a good reason : that state is required for the system to work.
So let's say we have an object that represent a user in our system.
When talking about users with our domain experts, we know that a user has an ID
and has an emailAddress
.
These informations are not implementation details. They're common knowledge for the community of objects the User
lives in.
Hold on a minute. Didn't you tell us that information should be private to an object ? You even have a whole chapter about getters and setters !
That's correct. But the reality is actually more subtle.
Information must be public in the system for objects to communicate. Objects communicate via messages, so they necessarily share informations to one another. In its most benign form, that information is just an intent : when you call PasswordHasher.hash
, you communicate the intent to hash something. That's information.
This method also need parameters to act upon. That's information too.
And when you deal with stateful objects, especially entities and value objects, you have to know that they're holding informations. By the book Object-Design from Rebecca Wirfs-Brock, they're information holder.
A user has an id and an e-mail address. There's nothing you can do about it.
However, the following is still a violation of Pragmatic Objects.
Why ? Because the method exposes the internal of how that information is represented. By looking at the method, we really are returning the property emailAddress
itself, removing the flexibility of User to change its implementation of that field.
Even worse.
The property is naked. All power has been given to the depending objects. All is lost.
Why is it a problem ? Imagine now that we wish to create a richer object to represent e-mail addresses, because, turns out, an e-mail address isn't just a string.
Now imagine we want to replace the property of the User
class with this brand new, incredible EmailAddress
class.
Damn. All the clients that used to call getEmailAddress()
are now all broken because they implicitly expected a string but now receive an EmailAddress
object. Shocks.
The reason is that our User object implicitly liked the getEmailAddress()
to the property emailAddress and the return type of that method was coerced into string. Now, it's coerced into EmailAddress
.
One simple fix would be :
Better, but what if another object wants to work with the EmailAddress
object directly instead of the raw value ? After all, raw value are mostly used at the boundaries of the system, either to save the object in storage or to show it to the user. They're rarely useful within the system itself.
So you see the problem with getters : they expose the internals of how the information is stored and represented within an object.
And that's the point I want to convey : Pragmatic Objects is not about hiding the information itself. Users have e-mail addresses and it's a fact of our system, period. However, the way that information is represented by the object is of no concern to the other objects of the system.
The information is public, the representation of that information is a private detail.
We're not encapsulating the information, but its representation.
So in this case, I'd ask my self :
Who needs to deal with the
EmailAddress
as an object ?Who needs to deal with the
EmailAddress
as a value ?
The answers are always the same :
Within the domain, you deal with objects
Outside the domain, in the infrastructure, you deal with values in order to store them / return them from JSON APIs
These are two different use cases serving two different actors, so I will need to ways of retrieving the same information.
Inside the Domain
Inside the domain, I'd deal in term of rich objects, so I would have that method in the first place.
Notice the type of the output value : it is no longer explicit but implicit. Here, as the creator and maintainer of the User class, I declare that getEmailAddress
will always return an EmailAddress
, no matter what. It is unrelated to the internal representation of my object. The fact that the property emailAddress
and the getter have the same return value is merely a coincidence.
Outside the Domain
When exposing the internals of the object to the outside world, there's really three ways to go :
Access that information without involving the object, by reading it directly from the database. This is a current practice in CQRS where the query side of the application deals only with data and information, and not with objects.
Have database bindings and converter so that the ORM can read properties without any accessors, using reflection or compile-time analysis of the code
Expose a data representation of the object by using the Expose Snapshots practice.
Summary
Object-Oriented programming really is about managing information and dependencies. Such information exist and isn't an implementation detail ; The detail is how this information is represented within the system.
Last updated