Determining what state or behaviors to expose on an object can be difficult. We all know the basic rules—encapsulate logic, only expose immutable value objects, and so on—but putting those rules into practice does not come naturally. In fact, given our heritage of anemic, relational models, it’s almost unnatural. Several years ago I remember reading about SOLID and other OOP principles and thinking to myself, how on earth would you persist that?
Of course not all objects are persistent, and having the benefit of experience I now know that the view, domain, and persistence models need not be one and the same. That being the case, there are incredible benefits to encapsulation, if done properly. But how exactly does one put aside relational habits?
One trick I have is to imagine objects have personalities and imagine how they would react if I asked certain questions or requested certain behaviors. For example, suppose I had a Person object and I wanted to know where they were from. If it were a real person, I would simply ask him, “Where are you from?”. The person would then think about the places he has lived, how long he has lived there, whether he had family there, etc. Eventually he would arrive at an answer and would provide it.
Now, my personal tendency would have been to model the Person object with a one-to-many collection of Residence (or similar) with public virtual getter and a protected/internal setter, which would map nicely onto a set of tables in a relational database using any number of ORM tools. And given a public PlacesOfResidence property, any caller can iterate through it and make whatever judgment he likes about where the person might be from.
Bringing that back to the real-life example, how would a person react if you (a stranger) walked up to them and asked for a complete history of all of the places they lived, when they lived there, how long they lived there, whether they have family there, etc.? And, given that information, would you then know definitively where the person was “from”? Of course not.
So the Person object needs a GetWhereFrom behavior that encapsulates that logic. Further, the full list of Residences should not be exposed via any public or protected/internal method. And finally, chances are that the GetWhereFrom behavior should not return a Residence, but rather some kind of immutable Location value object that does not reveal more than the question that was asked.
Now, knowing where the person is from, what else might one ask? How about, “Were you born there?” or “How long did you live there?” or “Do you have family there?” These are all excellent opportunities for encapsulated logic and could be realized as behaviors like WasBornIn(Location), GetLengthOfResidence(Location), HasFamilyIn(Location).
An object does not know who might call it, so all callers must be treated as strangers. They should not be trusted. An object should jealously guard its secrets (its state), only responding to the behaviors that are exposed, and only then with very simple and direct answers. This will reduce coupling and avoid leaky abstractions, allowing the freedom to change how the answers are calculated without impacting callers.
In the next post I’ll talk about the internal representation of state versus exposed state, and practical ways that proper encapsulation can improve maintainability.