OOP - Encapsulating Business Logic In Class Properties

The first “pillar” of Object-Oriented Programming (OOP) is encapsulation. If you have ever come to one of my conference sessions, you might hear me say…
 
If encapsulation isn’t done correctly, I have little hope that the other pillars of OOP are being done properly or at all!
 
For this article, I will be explaining how I helped one of the other junior developers on the team I work in and ended up teaching my entire team about the proper way to implement encapsulation in class properties.
 

Implementing New Business Rules

 
When the team member contacted me, he showed me a class similar to the one below.
  1. public class PropertyDemo  
  2. {  
  3.     public string BillingStartDate { getset; }  
  4.     public string EventDate { getset; }  
  5. }  
The new business rule he was told to implement was that if BillingStartDate is greater than the EventDate, then the EventDate should be set with the BillingStartDate value. I gave him advice and said that the check should be done in the property setter. After a few days, he showed me the modifications similar to the example below.
  1. public class PropertyDemo  
  2. {  
  3.     public string BillingStartDate { getset; }  
  4.     private string _eventDate;  
  5.     public string EventDate  
  6.     {  
  7.         get  
  8.         {  
  9.             if (DateTime.Parse(BillingStartDate) >DateTime.Parse(_eventDate))  
  10.             {  
  11.                 return BillingStartDate;  
  12.             }  
  13.             else  
  14.             {  
  15.                 return _eventDate;  
  16.             }  
  17.         }  
  18.         set  
  19.         {  
  20.             _eventDate = value;  
  21.         }  
  22.     }  
  23. }  
While this solves the new business rule, it’s not implementing encapsulation the way I would implement it. Let me explain why.
 

Encapsulating Properties

 
Since encapsulation is the first pillar of OOP, let us review the definition from Wikipedia.
 
OOP - Encapsulating Business Logic In Class Properties
 
In object-oriented programming (OOP), encapsulation refers to the bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing unauthorized parties' direct access to them.
 
Publicly accessible methods are generally provided in the class (so-called "getters" and "setters") to access the values, and other client classes call these methods to retrieve and modify the values within the object.
 
My additional rule for encapsulation states,
 
All data coming into a class (type) MUST be validated!
 
This goes back to the old saying “Bad data in, bad data out”. This is especially true for databases, and as developers, we need to make sure that does not happen. The place to implement rules like this one is to encapsulate that logic in the class and for properties, in the setter. Also, it makes better sense when thinking about performance, to determine if EventDate should be set to BillingStartDate once in the setter, not every time the getter is called.
 
When I told my teammate this, he said that it would only happen once due to deserializing the data from JSON. While that could be true now, this might not be valid in the future. I told him, when designing classes, you can not count on what order the properties would be set and there is no guarantee that there is even a value in BillingStartDate.
 
The .NET Framework did have a way to do this easier with Code Contracts, but unfortunately, the Visual Studio team has abandoned that project, with no replacement. I hope they bring it back in the future (I have submitted a ticket to Microsoft to try to get this done).
 
My teammate said he was told to put the logic in the property getter. Unfortunately, I had a hard time explaining why I do not prefer this to my team… until I showed them the code that I re-worked and explained it.
 

Fixing the EventDate Property

 
Fixing the EventDate property includes a few things with the first one validating the data in the property setter.
  1. Make sure the data encapsulated in the type is always valid, so I will move the logic into the setter.
  2. Using DateTime.Parse will cause an exception if the date string is not formatted correctly. This is very important since the type of these properties is a string. If it were my project, these properties would use DateTimeOffset.
  3. This change will force a change in the BillingStartDate property that I will discuss in the next section.
This is how I ended up changing this property,
  1. public string EventDate  
  2. {  
  3.     get  
  4.     {  
  5.         return _eventDate;  
  6.     }  
  7.     set  
  8.     {  
  9.         if (_eventDate == value)  
  10.         {  
  11.             return;  
  12.         }  
  13.   
  14.         //If we have a valid event date  
  15.         if (DateTime.TryParse(value, CultureInfo.CurrentCulture,
  16.                                 DateTimeStyles.None,   
  17.                                 out DateTime eventDate))  
  18.         {  
  19.             _eventDate = value;  
  20.   
  21.             // If we have a valid billing start date  
  22.             if (DateTime.TryParse(BillingStartDate,  
  23.                                     CultureInfo.CurrentCulture,  
  24.                                     DateTimeStyles.None,  
  25.                                     out DateTime billingStartDate))  
  26.             {  
  27.                 //Set event date to billing start date if greater  
  28.                 if (billingStartDate>eventDate)  
  29.                 {  
  30.                     _eventDate = BillingStartDate;  
  31.                 }  
  32.             }  
  33.         }  
  34.         else  
  35.         {  
  36.             throw new ArgumentNullException(nameof(value), "Invalid event date.");  
  37.         }  
  38.     }  
  39. }  
As you can see, I am using DateTime.TryParse since it does not throw an exception if the string is an invalid date. I even included globalization settings to make sure that this property will work “if” the project is needed to work properly with different languages in the future. Also, I only allow the logic if BillingStartDate is greater than EventDate if there is a valid date. If this were my project, I would encapsulate most of the code in the setter in a private method. Also, after working on this, I have added a new extension method for DateTime called Max in my open-source NuGet package. But we are not done.
 

Fixing the BillingStartDate Property

 
Now that we moved the logic to the setter in EventDate, what if BillingStartDate changes after the EventDate is set? Well, we need to write some logic around that too. Here is the new property code.
  1. public string BillingStartDate  
  2. {  
  3.     get  
  4.     {  
  5.         return this._billingStartDate;  
  6.     }  
  7.   
  8.     set  
  9.     {  
  10.         if (this._billingStartDate == value)  
  11.         {  
  12.             return;  
  13.         }  
  14.   
  15.         //Validate that the billing start date is valid.  
  16.         if (DateTime.TryParse(value, out DateTime billingStartDate))  
  17.         {  
  18.             this._billingStartDate = value;  
  19.   
  20.             //Set Event date if billing start date is greater than event date  
  21.             if (DateTime.TryParse(this.EventDate, out DateTime eventDate))  
  22.             {  
  23.                 if (billingStartDate>eventDate)  
  24.                 {  
  25.                     this._eventDate = value;  
  26.                 }  
  27.             }  
  28.         }  
  29.         else  
  30.         {  
  31.             throw new ArgumentNullException(nameof(value), "Invalid billing start date.");  
  32.         }  
  33.     }  
  34. }  
As you can see, I implemented similar logic in this property as in the EventDate property. This is proper encapsulation! Well, at least it’s correctto me.
 

Summary

 
As you can see in the article, we added a lot more code to the properties that will make sure that not only the business logic is encapsulated in the property, it ensures that the properties only store proper dates in the backing fields.
 
If you are new to encapsulation or need to learn how to do it better, I hope this article has helped. I also write about this subject a lot on this website. I hope you will read those articles too. If you have any comments, please make them below.