Only a fool worries about his beard while his head is on the chopping block.
One of the core ideas of object oriented programming is Encapsulation. The basic idea is that in order to improve the modularity of a class, service, or domain , we have to limit the amount of information shared about that class, service or domain and the amount of information that class, service or domain has about the surrounding system.
The most common example is class property and method access. Rather than allowing direct manipulation of class properties it is common practice to make the properties private and only allow access through getter and setter methods.
In the above implementation we are not using encapsulation for the Provider class. So the Consumer class can manipulate the amount and tax properties of the Provider directly. Without any encapsulation we create some very dangerous problems.
Now consider the same relationship with basic encapsulation or Data encapsulation.
With Data encapsulation, we can both control the data type of the provider class properties and change the name of these properties without worrying about breaking the consumer class.
The benefits of data encapsulation are increased control of data types and reduced cost of change. Both of which improve the stability and flexibility of the application. Of course you can find a million examples of this type of encapsulation online, because this is just the textbook definition of Encapsulation.
However this isn’t the end of the story. While these benefits are more than enough, there are far deeper implications and uses that are not often discussed that can be transformational when understood and implemented.
If we look at the basic encapsulation example, what exactly did we actually “hide” from the consumer class?
As we can see data encapsulation has hidden only 2 of the 5 details about the Provider class. If you’re like me then you know we can do better than that. The next level of encapsulation is what we will call Process encapsulation.
Process encapsulation moves the data manipulation processes to the data owner to further hide details about the class. Looking again at the before and after of process encapsulation we can see that we’ve hidden 4 of 6 details about the Provider class.
Of course in this example we must assume that the consumer class as a whole does more than just Consumer::getTotal Otherwise we don’t need the class and should use the Provider class directly.
Thus far we’ve hidden 4 out of 6 facts from the Consumer class. However this can be taken one step further. We can effectively hide 5 out of 6 details by using a technique I call Kicking the can.
As you can see from the above example we’ve hidden 5 of the 6 details of the Provider class from the Consumer class. So we might be tempted to think of this as just Data Encapsulation on the Consumer class.
But in reality all you have done is kicked the can down to whichever classes that need the total. Thus in order to hide the fact that the Provider has a total from the Consumer, you’ve exposed the existence of Provider to all Consumers of the Consumer class.
As well as exposing the relationship between the Consumer and Provider. The end result is creating dependencies between Provider and any class that uses the Consumer::getProvider method.
Of course “Kicking the can” isn’t always a bad thing. Like everything in software there are times when this technique is the right choice, however it should never be used across bounded contexts and only used when the alternatives would cost more. Using it without considering the cost/benefit will without a doubt lead to speghetti.
With that out of the way I’d like to introduce another form of encapsulation which in my opinion is one of the most powerful techniques for creating stable flexible systems. I call it Decision Encapsulation.
Although this technique isn’t all together rare, there are surprising little resources discussing its benefits or implementations. I think that is because it requires a more complex context to demonstrate the benefits. So it’s hard to find the right boundaries to do it justice.
In the above example we have a service that is responsible for providing some service to the user with a basic access permission check.
Looking at the implementation we can see that if the user has a company then we use the company's permissions to determine if the user has access to the service.
At first glance this may seem like a pretty reasonable implementation. We have good separation of concerns and decent encapsulation.
However, since we used the `Kick the can` technique for the User::company, we’ve split the User into two implicit subtypes. Users with companies and Users without companies.
It has also increased the complexity of our service, because we have to make distinctions between the two subtypes. As well as make it harder to change User class because it will forever have to implement the getCompany() method or we’ll break our service.
If we use Decision encapsulation here, we can reduce complexity, prevent subtyping and make our lives easier now and in the future.
The best part is the change is very minimal. All we need to do is move the decision to the Owner of the relationship which the decision is based upon. In this case the User, because the decision is based on the User’s relationship to the Company.
As you can see this approach simplifies the interface and the implementation of both the service and the user.
While it does increase the complexity of the User::allow method, this additional complexity can be justified by decoupling the service from the company and increasing encapsulation of the user.
If this was all, I believe it would be sufficient justification for using decision encapsulation, however we haven’t even considered reusability and stability benefits that this approach offers. If for instance we have multiple services, they can be implemented without implicit subtyping the user.