DEV Community

loading...

Encapsulate state and expose behavior when writing object-oriented code

scottshipp profile image scottshipp ・4 min read

A-PIE.

You ever heard of that? For some reason my mouth is watering.

A delicious fresh-baked pie

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!

Discussion

pic
Editor guide
Collapse
alainvanhout profile image
Alain Van Hout

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.

Collapse
jvarness profile image
Jake Varness

Not a bad topic. Encapsulation is definitely important. In C# you could also make your sets private as well. This would allow you to easily get the value of a property, but not allow you to easily modify it.

Collapse
pnunezcalzado profile image
Pedro Nuñez Calzado

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.

Collapse
unruledboy profile image
Wilson Chen

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.

Collapse
telexen profile image
Jake C

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.

Collapse
scottshipp profile image
scottshipp Author

Help us understand the motivation for a private setter. Why not provide only a getter and not have a setter at all?

Collapse
nicokno profile image
Nicolas Knoll

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.

Collapse
bennchristensen profile image
BennChristensen

"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.

Collapse
scottshipp profile image
scottshipp Author

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.