Gigi Labs

Please follow Gigi Labs for the latest articles. Programmer's Ranch no longer has its domain, so please update your bookmarks and links to programmersranch.blogspot.com.

Friday, August 9, 2013

C# OOP: Encapsulation and Properties

Hi everyone!

Some recent articles have discussed different aspects of Object Oriented Programming. Today we'll talk about controlling access to members (variables or methods) of our classes.

Let's begin with a scenario. We have an accounting program that keeps track of a person's money. First, we need to use the following library:

using System.Collections.Generic;

Then we declare a Transaction class to contain the details of each transaction (deposits, withdrawals... basically anything that affects the amount of money in the account):

    class Transaction
    {
        public DateTime date;
        public String detail;
        public float amount;
       
        public Transaction(DateTime date, String detail, float amount)
        {
            this.date = date;
            this.detail = detail;
            this.amount = amount;
        }
    }

We also need an Account class to keep the overall account information, as well as a list of all the transactions in the account:

    class Account
    {
        public String name;
        public float balance;
        public List<Transaction> transactions;
       
        public Account(String name)
        {
            this.name = name;
            this.balance = 0.0f;
            this.transactions = new List<Transaction>();
        }
       
        public void Show()
        {
            Console.WriteLine("Account for {0}", name);
            Console.WriteLine();
           
            foreach (Transaction transaction in transactions)
            {
                Console.WriteLine("{0:yyyy-MM-dd}  {1, -30}  {2}",
                        transaction.date, transaction.detail, transaction.amount.ToString("0.00").PadLeft(8' '));
            }
           
            Console.WriteLine();
            Console.WriteLine("Balance: {0:0.00}"this.balance);
        }
    }

We can now see how this works with some sample code in Main():

            Console.Title = "C# Encapsulation with Accounting";
           
            Transaction trans1 = new Transaction(new DateTime(20130807), "Deposit: My first salary"20.0f);
            Transaction trans2 = new Transaction(new DateTime(20130808), "Withdrawal... need cash", -15.0f);
            Transaction trans3 = new Transaction(new DateTime(20130809), "Deposit: Birthday present"10.0f);
           
            Account account = new Account("Bill Gates");
           
            account.transactions.Add(trans1);
            account.balance += trans1.amount;
            account.transactions.Add(trans2);
            account.balance += trans2.amount;
            account.transactions.Add(trans3);
            account.balance += trans3.amount;

            account.Show();
           
            Console.ReadKey(true);

So here we've created an account for Bill Gates and added three transactions. The Show() method gives us a picture of the account status and history:


That's great. But what if the guy writing the code in Main() forgets to update the account balance? The sum of the transactions won't agree with the value in the balance member variable. We could provide a method to take care of this in Account:

        public void AddTransaction(Transaction transaction)
        {
            transactions.Add(transaction);
            this.balance += transaction.amount;
        }

...and while this is certainly useful, it doesn't really solve the problem. Because while a programmer might use this method, there is nothing to keep him from updating transactions and balance separately, the old way, as illustrated earlier. Even worse, someone might actually tamper with them, potentially removing transactions or modifying the balance into something different.

The one thing we've been doing wrong all along is declaring all member variables and methods in our classes as public. When they're public, it means anyone can touch them. We can restrict access to members by using a protected or private access modifier instead of public (there are others, but they're not important at this stage).

Once we switch the member variables in the Account class to private:

        private String name;
        private float balance;
        private List<Transaction> transactions;

...then the program won't compile:


That's because we're accessing these private member variables directly from within Main(). When a variable is private, it means that only code within the same class can access it. So it's ok for the AddTransaction() method we added earlier to use the transactions member variable, but the code in Main() can't. Instead, the code in Main() must be refactored to use AddTransaction():

            account.AddTransaction(trans1);
            account.AddTransaction(trans2);
            account.AddTransaction(trans3);

With this change, it compiles just as well. So we've just seen how we can prevent users of a class from tampering with its internal state, and yet still do useful stuff with it via the methods it does expose as public. This is called encapsulation, and it effectively means "data hiding". In OOP it is usually the case that our classes provide a public interface by which other objects can use them, but they hide all the rest from public view.

Naturally, it is often the case that other objects want to access member variables for legitimate reasons. In that case, instead of making the member variable public, we add methods allowing access to them:

        public String GetName()
        {
            return this.name;
        }
       
        public void SetName(String value)
        {
            this.name = value;
        }
       
        public float GetBalance()
        {
            return this.balance;
        }

These are called getter and setter methods, because they allow you to retrieve and modify the variable. A setter can be omitted (as with the balance variable) if that variable is meant to be read-only.

Although using getter and setter methods is a great way of using encapsulation in any OOP language, C# provides the use of properties, which do the same thing but are arguably more elegant:

        public String Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }
       
        public float Balance
        {
            get
            {
                return this.balance;
            }
        }

Properties are actually converted to getter and setter methods during compilation, but this process is transparent to you (the programmer). Using them in code is pretty easy:

Console.WriteLine("{0} has {1} Euros in his account.", account.Name, account.Balance);

Right, so what about the protected access modifier? It's like private, but also allows subclasses to access a class's members. You should use it sparingly, or not at all. Scott Meyers explains why in his excellent book "Effective C++: 55 Specific Ways to Improve Your Programs and Designs". When you make something public, there are potentially infinitely many places from where it can be accessed and modified. So if you decide to change that variable (e.g. remove it and replace it with a computation), you have to refactor all those places. If something is protected, it can be accessed from any subclass, so there are still porentially infinitely many places from where it can be modified. If you want a subclass to access a variable, your best bet is to encapsulate it using a getter/setter pair or a property, and have the subclass access that.

To see this idea in action, let's remove the Account class's balance variable and replace it with a sum of the transaction amounts:

         public float Balance
        {
            get
            {
                float bal = 0.0f;
               
                foreach (Transaction transaction in this.transactions)
                {
                    bal += transaction.amount;
                }
               
                return bal;
            }
        }

So like this, any other classes accessing the Balance property may continue to do so without needing to refactor anything. In our case we only need to make some small changes in the Account class itself (no need to maintain a separate balance variable any more in the constructor and in AddTransaction(), and use the property instead of the variable in Show()). But if we had to refactor a hundred other classes to remove the balance variable, it would have been a very expensive change.

Cool. So in this article we learned why we should hide certain internal class data from outside code that might tamper with it - a practice called encapsulation. Encapsulation is so important that, together with inheritance and polymorphism, it is known as one of the Three Pillars of OOP. We also learned about access modifiers (public, protected, private), getter/setter methods, as well as properties - something unique to C#.

From these OOP articles you might begin to see what OOP is really about: the complex relationships between objects make designing OOP software quite a challenge. It's also quite fun. :)

I hope you enjoyed this article, and check back for more! :)

No comments:

Post a Comment