Proper Type Encapsulation - Part Two

In part 1 of this article, I explained how to implement proper data encapsulation. In part 2 I want to talk about encapsulating business logic. I see this missing in a lot of type design, especially when using an ORM like Entity Framework.

It’s the job of the architect and coder of that type to make sure that the business logic is encapsulated in it. This ensures that whoever is using the type does not need to know deep knowledge on how the business logic works. They should just create it and then just call properties and methods. For example, when you call BeginTransaction() from the SqlConnection type, do you know exactly what that method does? Of course not, that was the job of the architect and coder of that method to include that in SqlConnection. If something goes wrong, then an exception will be thrown (hopefully).

In part 1, I discussed how important validating the data coming into the type. As I said, bad data in… bad data out. Some of you might think that code to connect to the database is encapsulation and it is, but below I will be focusing on the logic that usually the architect needs to come up with to make it easier on any developer consuming that type without needing to have domain logic of. I personally try to make sure a developer at any level should be able to quickly understand how to use that type, without reading the documentation.

Encapsulate Creating a Collection

Let’s say you have a type called OrderCollection that contains a collection of Order. By design, there cannot be and orders with a duplicate id’s or maybe even order data? Sure, that can be done by creating proper indexes in the database, and it should. There are two possible issues with this way of thinking.

  1. Indexes can hurt the performance of the database, especially inserts. They can also take up a lot of room on a disk drive.
  2. As a developer, I shouldn’t need to look at the documentation or the database to figure out what constitutes a unique order. What if I don’t have access to see that info in the database? The more likely scenario is it isn’t documented. Even if it is, the documentation is likely to be out of date.

What makes a unique order should be encapsulated into the type so all I must do as the consumer of that type is something like this,

  1. public sealed class OrderCollection: List<Order>  
  2. {  
  3.     public static OrderCollection Create(IEnumerable<Order> orders)  
  4.     {  
  5.         if (orders == null)  
  6.         {  
  7.             throw new ArgumentNullException(nameof(orders),   
  8.               "Orders cannot be null.");  
  9.         }  
  10.           
  11.         var ordersAdded = new OrderCollection();  
  12.          
  13.         foreach (var order in orders.Where(o => o != null))  
  14.         {  
  15.           ordersAdded.Add(order);  
  16.         }  
  17.          
  18.         return ordersAdded;  
  19.     }  

As you can see, the way to create a collection of Orders from a collection is to use this method. In this example, I’m just checking to make sure that the order isn’t already in part of the collection in memory.

Here is example code for the Add method,

  1. public void Add(Order order)  
  2. {  
  3.     if (order != null && base.Contains(order) == false))  
  4.     {  
  5.         base.Add(order);  
  6.     }  
  7. }  

I’m only checking to make sure that the order is not already in the collection in memory, but other logic can be easily added here. Also, it would be easy to add an OnAdded method to notify other objects through an event that an order has been added or OnRemoved etc., you get the idea.

Encapsulate Retrieving a Subset of a Collection

Now, that we have a collection of Orders, what if I want to retrieve a list of the open or closed orders? What Order properties indicate that it’s an open or closed order? As the consumer of the type, I shouldn’t need to know this. If you ask 5 of your colleagues what is an open order, you most likely will get 5 answers if you get them at all. Usually, I just see fingers wave at someone with the statement “ask that person”. Then they point to someone else and so on and so on!

As the architect of the type, it’s YOUR JOB to encapsulate this logic into the type. Take the example below,

  1. public IEnumerable<Orders> OpenOrders()  
  2. {  
  3.     var openOrders = base.Where(o => o.ClosedOn.HasValue() == false);  
  4.     return openOrders.AsEnumerable();  
  5. }  

In the example above, I’m checking to see if the ClosedOn property has a value. This is pretty simple but could easily be more complicated. A better way would be to have a IsClosed or Status property that has a private setter. This forces the encapsulation of this logic.

There are many patterns to help with encapsulation too, such as Fluid Validation and more. Encapsulating this type of logic into the type also is much better for code maintenance. If there is a bug or a change in the logic, there is only one place to change it (and test it) instead of throughout many projects. Just a little more time when architecting the type will save your company a lot of time and money down the road.

Summary

In the examples above I used a collection type to show encapsulation but the concepts I talked about can apply to any type that you write. How do you implement encapsulation in your types? Please make a comment below.


Similar Articles
McCarter Consulting
Software architecture, code & app performance, code quality, Microsoft .NET & mentoring. Available!