Architecture & DesignHiding Data within Object-Oriented Programming

Hiding Data within Object-Oriented Programming

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

This is the seventh installment in a series of articles about fundamental object-oriented (OO) concepts. The material presented in these articles is based on material from the second edition of my book, The Object-Oriented Thought Process, 2nd edition. The Object-Oriented Thought Process is intended for anyone who needs to understand the basic object-oriented concepts before jumping into the code. Click here to start at the beginning of the series.

Now that we have covered the conceptual basics of classes and objects, we can start to explore specific concepts in more detail. Remember that there are three criteria that are applied to object-oriented languages: They have to implement encapsulation, inheritance, and polymorphism. Of course, these are not the only important terms, but they are a great place to start a discussion.

In the previous article, this article, and several of the ones that follow, we will focus on a single concept and explore how it fits in to the object-oriented model. We will also begin to get much more involved with code. In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the J2SE 1.4.2 SDK (software development kit) to compile and execute these applications and will provide the code listings for all examples in this article. I have the SDK 1.4.0 loaded on my machine. I will provide figures and the output for these examples. See the previous article in this series for detailed descriptions for compiling and running all the code examples in this series.

Checking Account Example

Recall that in the previous article, we created a class diagram that is represented in the following UML diagram; see Figure 1.

Figure 1: UML Diagram for Checking Account Class

This class is designed to illustrate the concept of encapsulation. This class encapsulates both the data (attributes) and methods (behaviors). Encapsulation is a fundamental concept of object-oriented design—all objects contain both attributes and behavior. The UML class diagram is composed of only three parts: the class name, the attributes, and the behaviors. This class diagram maps directly to the code in Listing 1.

class CheckingAccount {
   private double balance = 0;
   public void setBalance(double bal) {
      balance = bal;
   };
   public double getBalance(){
      return balance;
   };
}

Listing 1: CheckingAccount.java

This direct mapping from class diagram to code (and back) is another important issue when designing classes. This mapping not only helps with the design of the class, but also with the maintenance of the class. Multiple class diagrams are used to design an object-oriented system and make up what is termed an object model. Object models will be discussed at length in later articles.

Once a system is complete, the documentation pertaining to the design is obviously very important. The deployment of a new application marks the beginning of the maintenance phase, which will continue for the lifetime of the application and contain many, many bug fixes, updates, and enhancements to the system. While the object-model is a great design tool, it is also a great maintenance tool.

However, as with all documentation, an object model that is not updated as the application changes is at best useless, and at worst misleading. Anyone who has used flowcharts knows exactly what the problem is. For example, consider an application that is deployed with flow charts included in the final documentation. If a bug is identified and fixed in the code, unless the change in logic is retrofitted to the flow chart, the flow chart is out of date and does not represent the current application. In the future, when another programmer, or perhaps even the original programmer, revisits the documentation, it can be misleading. This may lead to a major problem. The same problem can occur when classes change and the class diagram is not updated properly to reflect the changes.

Why have we taken this interlude about updating documentation at this point? Because there now are tools to manage much of the documentation process and I believe that anyone wanting to learn object-oriented design must integrate the knowledge of these tools into their thought process as early as possible. In short, the process of creating class diagrams and mapping them directly to code (and back) is tracked, and somewhat controlled, by software tools. In future articles, we will get into much more detail about the tools themselves. Right now, just be aware that the class diagrams and the code are tightly coupled.

Note: Some of the products that can help with the process are Rational Rose, owned by IBM and Visio, owned by Microsoft.

Data Hiding

Returning to the actual CheckingAccount example, we can see that while the class contains both attributes and behavior, not all of the class is accessible to other class. For example, consider again the balance attribute. Note that balance is defined as private.

private double balance = 0;

We proved in the last article that attempting to access this attribute directly from an application would produce an error. The application that produces this error is shown in Listing 2.

class Encapsulation {

   public static void main(String args[]) {
      System.out.println("Starting myEncapsulation...");
      CheckingAccount myAccount = new   CheckingAccount();
      myAccount.balance = 40.00;
      System.out.println("Balance = " + myAccount.getBalance());
   }

}

Listing 2: Encapsulation.java

The offending line is the where this main application attempts to set balance directly.

myAccount.balance = 40.00;

This line violates the rule of data hiding. As we saw in last month’s article, the compiler does not allow this; however, it fails to set balance to 40 only because the access was declared as private. It is interesting to note that the Java language, just as C++, C#, and other languages, allows for the attribute to be declared as public. In this case, the main application would indeed be allowed to directly set the value of balance. This then would break the object-oriented concept of data hiding and would not be considered a proper object-oriented design.

This is one area where the importance of the design comes in. If you abide by the rule that all attributes are private, all attributes of an object are hidden, thus the term data hiding. This is so important because the compiler now can enforce the data hiding rule. If you declare all of a class’s attributes as private, a rogue developer cannot directly access the attributes from an application. Basically, you get this protection checking for free.

Whereas the class’s attributes are hidden, the methods in this example are designated as public.

public void setBalance(double bal) {
   balance = bal;
};
public double getBalance(){
   return balance;

};

In our design, these two methods represent the public interface to the CheckingAccount class. These methods are the only way that an application can get access to the balance attribute. These types of methods are sometimes referred to as getters and setters, for obvious reasons. Some books combine the concept of getters and setters and call them mutator methods, or simply mutators. In any case, these getters and setters represent the way that access to an attribute is controlled. And object-oriented design is all about control.

Not all attributes will have both a getter and a setter. There are situations where you may want to allow a user the ability to inspect an attribute, but not change it—in effect read permission only. For example, let’s assume that when we design our CheckingAccount class, we only want to allow outside applications to inspect balance and not change it. We could write the class as seen in Listing 3.

class CheckingAccount {
   private double balance = 0;
   private void setBalance(double bal) {
      balance = bal;
   };
   public double getBalance(){
      return balance;
   };
}

Listing 3: CheckingAccount.java with ‘read only’ access to balance

Notice now that setBalance() is designated as private.

private void setBalance(double bal);

   balance = bal;
};

This will prohibit an application from calling the setBalance() method, thus, in effect stopping the application from being able to change balance at all. Take a look at the code in Listing 4.

class Encapsulation {

   public static void main(String args[]) {
      System.out.println("Starting myEncapsulation...");
      CheckingAccount myAccount = new   CheckingAccount();
      myAccount.setBalance(40.00);
      System.out.println("Balance = " + myAccount.getBalance());
   }

}

Listing 4: Encapsulation.java with private access to setBalance()

Whereas this code worked perfectly when setBalance() was public, it now produces the following error:

Figure 2: Output with private setBalance().

You can see in Figure 2 that, when we attempt to compile this code, the compiler itself balks and does not allow the compilation to complete successfully. The following error is produced.

Encapsulation.java:8: setBalance(double) has private access in
CheckingAccount

                myAccount.setBalance(40.00);
                         ^

Thus, any application written with this CheckingAccount class will be unable to successfully call the setBalance() method. One of the design techniques that this illustrates is that, while all of the attributes of a class should be designated as private, not all the methods will necessarily be public.

What we have done with this design is limit the ability to set the value of balance to the class itself. In short, the setBalance() method is not part of the public interface of the class—it is now a private implementation of the CheckingAccount class.

Why would we even want to do this? Obviously, at some point the value of balance needs to be set. However, think about how an actual checking account really works. You, as a user of the account, have the ability to inspect your account balance, but you certainly can’t set it directly. Doing this would be a major breach of security! So how can we model this in the design of our CheckingAccount class?

First, let’s consider how you would actually add money to your account. The obvious answer to this question is that you would deposit money into your account. Thus, we can add a behavior called deposit() to the CheckingAccount class. Consider the UML class diagram in Figure 3.

Figure 3: UML Diagram for Checking Account Class with deposit() method

With this version of the CheckingAccount class, we now have made the class more like the real world—we make a deposit. The code that implements this class is found in Listing 5.

class CheckingAccount {
   private double balance = 0;
   private void setBalance(double bal) {
      balance = bal;
   };
   public double getBalance(){
      return balance;
   };
   public double deposit(double dep){
         setBalance(balance + dep);
      return balance;
   };
}

Listing 5: CheckingAccount.java with deposit() method

Here is where the private implementation of setBalance() comes into play. Notice that rather than changing the value of balance directly, the deposit() method actually uses the setBalance() method. Even though setBalance() is private, the deposit() method can call it successfully because they are both members of the same class.

In this case, our application actually makes a deposit by calling the deposit() method, as shown in Listing 6.

class Encapsulation {

   public static void main(String args[]) {
      System.out.println("Starting myEncapsulation...");
      CheckingAccount myAccount = new   CheckingAccount();
      myAccount.deposit(40.00);
      System.out.println("Balance = " + myAccount.getBalance());
   }

}

Listing 6: Encapsulation.java using deposit()

Why would we bother to go through all of these gyrations? There are several reasons, but the primary one is that this is how the real world works. You will notice a common theme as you study object-oriented design; the goal is to model real-world activities as closely as possible. In short, if you have a checking account with a balance of $1000, and want to make a deposit of $100, you don’t go to the ATM and simply set your balance to $1100. In theory this would work, but it opens the door to honest mistakes as well as not-so-honest misconduct. Thus, you deposit $100 and let the bank figure out how to add it to your balance.

Besides the real-world modeling advantage, another advantage here is that we can control access to balance more closely. Basically, there is only one place and one place only, in the code that can actually change balance. Notice that the deposit() method does not directly change balance; to accomplish this, it must call setBalance().

Remember the example in last month’s article where we did some range checking when we attempted to change balance? The same rule holds now that setBalance() is private.

private void setBalance(double bal) {

   if (bal < 0) {
      System,out.println ("Error!");
   } else {
      myAccount.balance = bal;
   }

It is possible that different points in the application may need to change balance. For example, there may be a method called addInterest() that needs to increase balance by the amount of interest earned. This is perfectly fine; however, to accomplish this, addInterest() must call setBalance().

One interesting note here is that this type of access control is not enforced by the compiler. If, for instance, deposit wanted to change the balance directly, it can.

public void deposit(double dep){
      balance = balance + dep;

};

This will not cause a compiler error but may well violate a programming practice established by you organization. This type of programming mistake would be caught during a code review. In any event, any direct changing of balance is limited to the actual class. No application could directly set balance.

We can extend our model to include withdrawal capabilities. Figure 4 shows the UML class diagram that corresponds to our design addition.

Figure 4: UML Diagram for Checking Account Class with withdrawal() method

Just like the deposit() method, we can create a withdrawal() method that utilizes setBalance().

public void withdrawal(double with){
   setBalance(balance - with);
};

Thus, our new CheckingAccount class is shown in

class CheckingAccount {

   private double balance = 0;
   private void setBalance(double bal) {
      balance = bal;
   };
   public double getBalance(){
      return balance;
   };
   public void deposit(double dep){
      setBalance(balance + dep);

   };
   public void withdrawal(double with){
      setBalance(balance - with);

   };
}

Listing 6: CheckingAccount.java with withdrawal() method

In effect, we now have the following components to our CheckingAccount class.

  1. The private attributes
    1. balance
  2. The private implementation
    1. setBalance()
  3. The public interfaces
    1. deposit()
    2. withdrawal()
    3. getBalance()

It is important to understand that there may be some private implementation. In this CheckingAccount example, setBalance() is part of that private implementation. Thus, classes can have both public and private methods. In short, the application can call the public interface methods, but can’t access either the private attributes or the private methods.

Conclusion

In this month’s article, we further explored encapsulation by delving deeper into the topic of data hiding. Data hiding is one of the most important concepts of object-oriented design. Data hiding provides a much higher level of security for your class as well as providing many programming advantages.

Next month, we will continue our banking theme by designing a class for an ATM—an example that is quite popular in illustrating object-oriented design concepts.

About the Author

Matt Weisfeld is an Assistant Professor at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a part of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET, as well as various Web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management.

The articles in this series are adapted from The Object-Oriented Thought Process 2nd Edition (published by Sams Publishing). Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories