A-PIE.
You ever heard of that? For some reason my mouth is watering.
A-PIE is not only a delicious baked dessert with a flaky crust and a warm gooey inside, it's a little acronym/mnemonic to help remember the four most important principles of object-oriented programming (OOP):
- Abstraction.
- Polymorphism.
- Inheritance.
- Encapsulation.
Encapsulation is what I want to talk about today. It seems like functional became the new exciting shiny thing and everyone forgot about OOP, even in OOP languages. Let's reset a little bit. If you write in an OOP language (which is a lot of them!), if OOP principles are baked into the design of your language, then one of the worst things you can do is forget them. That would be like holding a knife by the blade and trying to cut cheese with the handle. You're gonna get sliced!
Another way to say it is it's going to be like driving an '85 Honda Civic down the power line trail when there's a paved road right next to you. This is a story for another time, but a friend and I attempted this once and let me tell you from experience: it's not pretty unless you want to see some bent metal...
Sorry! Getting off track here. Let's get back to encapsulation.
So the concept of "data classes" has become quite vogue these days. Kotlin has direct support for data classes and Scala has case classes. These are JVM languages. Java has no direct equivalent, but it's interesting to note that both approaches are basically shortcut ways to create the classic JavaBean, which is a fancy word for a bag of data with public accessors and mutators.
C# gets at the same idea with automatic properties. If you look at the documentation for automatic properties, there's a prime example of the fly in my ointment today. In the example, a Customer
class is defined, which is then mutated by code outside the class:
Customer class
// This class is mutable. Its data can be modified from
// outside the class.
class Customer
{
// Auto-Impl Properties for trivial get and set
public double TotalPurchases { get; set; }
public string Name { get; set; }
public int CustomerID { get; set; }
// other code elided . . .
}
Program:
class Program
{
static void Main()
{
// Intialize a new object.
Customer cust1 = new Customer ( 4987.63, "Northwind",90108 );
//Modify a property
cust1.TotalPurchases += 499.99;
}
}
They even put a comment on there: This class is mutable. Its data can be modified from outside the class.
Shame, shame, shame I know your name C#!!!!!!
This is simple Object-Oriented 101! When the goal is to track total purchases, expose that functionality as public behavior on the Customer class, and encapsulate the data needed to make the behavior possible. The code should look something like:
Customer class
// This class' data cannot be modified from outside the class.
class Customer
{
private double TotalPurchases;
private string Name;
private int CustomerID;
// other code elided . . .
public void AddPurchase( DollarAmount amt ) {
TotalPurchase += amt;
}
}
Program:
class Program
{
static void Main()
{
Customer cust1 = new Customer ( 4987.63, "Northwind", 90108 );
cust1.AddPurchase(499.99);
}
}
See how the properties of the Customer
class are now private
, private
, private
?
That's right! You should keep your privates private. This is apparently not only a lesson I have to keep teaching my two-year-old, but a relevant and timeless adage for programmers of all ages.
Now, there are all kinds of reasons for keeping your privates private. A big one is that this makes it easier for the program to evolve! It's very possible that we'll want to move on from tracking just a DollarAmount
to something more in the future. We might perhaps want to store a whole list of transactions instead.
So I don't know about you, but I'd rather get a sharp poke in the eye than spend my afternoon tracking down all references to TotalPurchases
and yanking them out of whatever tangled weeds another programmer lost them in. In a good OO design where we kept TotalPurchases
private, it's a one-and-done change. I would simply update the innards of the Customer
class and get all that evolution for free!
Of course, it's really your choice. No one can tell you how to write your code. If you don't think you're going to have trouble with "data classes" separate from "behavior classes" in your application, by all means make a good go at it. But trust me, even if it might be fun one time to see how it feels to stick your finger in a light socket, you're not gonna want to do it again. If I can help save you the trouble, then I'll feel like I did my job in writing this article. Best of luck!
Top comments (9)
Keep in mind that the ultimate goal here is to have code that does its job and is maintainable. OOP is a tool that sometimes helps with the latter, and sometimes doesn’t. As to data classes, in which ever form, these are useful when state and behaviour reflect separate concerns (which is more often so than the average OOP introductory tends to suggest).
That said, OOP teaches some great lessons on encapsulation in particular, and separation of concerns in general.
Not a bad topic. Encapsulation is definitely important. In C# you could also make your
set
s private as well. This would allow you to easilyget
the value of a property, but not allow you to easily modify it.I'm not a Software Engineer, just a building architect with some programming knowledge. It so interesting the topic of the wrong use of the languages.
In my mind, the getter and the setter are enough to provide encapsulation, because the only "forbidden" thing in OOP is to make public fields.
Once you do both a getter and a setter in a public property, you are effectively isolating the private field from direct accessing, and you can leave those empty or implement the code you need to control the access.
In the case of the auto property, the compiler creates for you the private field. The concept of private setter is needed to provide access to the property within the class.
I don't agree on this. Customer is a merely a POCO here.
If you would really want to provide a proper encapsulation, then we would have provided TotalPurchase as public so the actual biz logic can read, use and persist.
You could always use the intent of the feature, and make the auto-property setter private, then implement your domain use case mutator function to encapsulate logic separately.
This would be preferable to writing lots of useless code just because you're used to doing it in a language that refused to come up with a better way.
Help us understand the motivation for a private setter. Why not provide only a getter and not have a setter at all?
Very refreshing. I'd like to see more explanations of this kind. Too much of these basic principles get burried in too large blog posts.
"They even put a comment on there: This class is mutable. Its data can be modified from outside the class."
No it is not, in C# the field is autogenerated when you use auto-properties.
Hi Benn, that's the comment from the Microsoft documentation (linked in the article). The data is mutable because it can be changed. It can be changed from outside the class via the set method. They are right.