http://www.developer.com/

Back to article

Exploring Encapsulation


June 30, 2004

Introduction

This is the sixth 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 this article, and several 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.

Encapsulation

en7cap7su7late (n-kps-lt) also in7cap7su7late: To encase in or as if in a capsule.

Definition from [http://www.dictionary.com]

Remember that encapsulation means that the attributes (data) and the behaviors (code) are encapsulated in to a single object. In other models, namely a structured model, code is in files that are separate from the data. An object, conceptually, combines the code and data into a single entity. To illustrate, let's begin immediately with a code example.

For this example, we will create a very simple, but functional, checking account object. At first pass, all we want our checking account object to do is hold the value representing the balance of the account, set the balance, and return the balance. It is always good to start simple and work your way to more complicated things. The UML diagram for this example can be seen in Figure 1.

Figure 1: UML Diagram for Checking Account Class

Here is the corresponding code for our simple checking account object:

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

Listing 1: CheckingAccount.java

Remember that to create objects we must first create the class (or template) of the object. Listing 1 is a class called CheckingAccount, which satisfies the requirements that we had regarding the class. This class has an attribute called balance, which will hold the balance of the CheckingAccount. The class also has two methods, one called setBalance(), which changes balance and getBalance(), which returns the value of balance.

The methods setBalance() and getBalance() are often called getters and setters (for obvious reasons). In many cases, an attribute of the object will have both a getter and a setter. This is not always the case and we will discuss this concept later in the article. What IS always the case is that the getters and setters are used to control access to the attributes and this is a key part of encapsulation.

Before we explore this code in much more detail, let's actually use this simple class. It is important to know that the class, as it is written in Listing 1, cannot be executed directly. In short, the class is not an application. To actually run something, you must create an application that may or may not use a class you have written. In Java, this application must start with a main() method as the entry point. Here is an example of an application that uses our CheckingAccount class to instantiate and use a CheckingAccount object.

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 2: Encapsulation.java

All this application does is instantiate a CheckingAccount object, called myAccount, set the value of balance to 40.00 and then print that balance out. Figure 2 presents a screen shot of the output of this application.



Click here for a larger image.

Figure 2: Checking Account application output.

Compiling and Executing These Examples

Before we proceed any further, let's define how the code listings in this article are compiled and executed. There are many different options that may be used to accomplish this task. As noted previously, we will be using the J2SE 1.4.2 SDK to compile and execute these applications. Using this Java SDK has a number of advantages. First, it is readily available for personal use at the Sun Microsystems Web site. Second, it is easy to move this code from one platform to another. Third, using the SDK provides experience in using a console-based platform. Some people may not consider the third point as an advantage. I believe that it is very helpful when learning a language and/or a concept to use a language at its basic syntax level. While many may balk at using a DOS or Unix shell, gaining experience with a console application is a definite advantage even with the state-of-the-art of current graphical use interfaces.

Download and install the SDK on your machine. Accept the default location for installation directories on your C: drive.

Now, let's compile and run this application on a Windows platform (the same techniques will work equally well on a Unix platform). For ease of use, you may want to use the same names that I use to create my directories. To begin, create a directory called example. This directory will contain the files for this example. In this directory, place the files from Listing 1 and Listing 2. These files must have the same name as the name of the classes they represent.

CheckingAccount.java

Encapsulation.java

To compile these classes, I like to create a batch file in DOS I call make.bat. Listing 3 shows what this file looks like.

REM Compile Java FilesC:\j2sdk1.4.0\bin\javac -classpath . CheckingAccount.javaC:\j2sdk1.4.0\bin\javac -classpath . Encapsulation.java

Listing 3: make.bat

I like to use the batch files because I often have multiple versions of the SDK on my machine for testing proposes. Using these batch files ensures that I am using the specific version of the SDK that I want. The .classpath option ensures that the files used in compilation are from the current directory, in this case the directory called example.

To execute the application, I use a similar batch file that I call go.bat. Listing 4 shows what this file looks like.

REM Run the ApplicationC:\j2sdk1.4.0\bin\java -classpath . Encapsulation

Listing 4: go.bat

Data Hiding

Perhaps the most important object-oriented concept is that of data hiding. Remember from earlier columns that data hiding means access to all of an object's attributes is tightly controlled. This is accomplished by making all the attributes private. In the CheckingAccount class, we only have a single attribute:

private double balance = 0;

This attribute is hidden from all other objects because of the private designation. To prove this, let's try to access the balance attribute directly from the myAccount object. Consider the following code:

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 5: Encapsulation.java (trying to directly access balance)

The following line of code illustrates what we mean by private, hidden data. In this line of code, the application object is trying to directly set balance to 40.00.

myAccount.balance = 40.00;

However, because the attribute is defined as private, the compiler will not allow this. Figure 3 shows how the compiler reacts to compiling the code in Listing 5.



Click here for a larger image.

Figure 3: Output with private balance.

Pay special attention to the error message that the compile provides:

Encapsulation.java:8: balance has private access in CheckingAccountmyAccount.balance = 40.00;

The complier realizes that the programmer designed the Checking Account class with balance as private. Thus, the compiler enforces this concept—balance is strictly off-limits to any object other than its own instance of myAccount.

You may ask, "What happens if balance is declared as public?"

public double balance = 0;

In this case, the code in Listing 5 executes cleanly. Try making this change to the code. Then, compile and execute it. It will work, but this violates the concept of data hiding. And this is a very important point. Data hiding, as with many other object-oriented concepts, is a design decision. In short, if you design the system with your attributes as private, the compiler can help you with enforcing data hiding. However, if you design your classes with public attributes, you have not designed your application in an object-oriented manner.

Why is data hiding so important? As stated earlier, perhaps the most important reason is access to the attributes. Consider the following line of code in an uncontrolled public access to the balance attribute.

myAccount.balance = -40.00;

In this line, balance is set to a negative number, -40. Obviously, we probably do not want to set a bank balance to a negative number (overdrafts notwithstanding). To prevent this, we would have to do a simple check in the code.

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

This works all right when there is only a single place to set balance. However, if we set the balance in multiple places, we would have to have this error checking in all of these locations.

One of the advantages of using a setter is that we can control access to the attribute and also do the error checking in a single place.

public void setBalance(double bal) {

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

Thus, by using a setter to control access to the attribute, we can ensure that balance NEVER gets set to a negative number. As an added benefit, if at some time that you decide that the minimum balance should be 100 (perhaps the overdraft limit), you only have to make the change to the error checking code in one place. I can set the balance in multiple places, and they all are controlled by the code in setBalance().

myAccount.setBalance(40.00);myAccount.setBalance(50.00);myAccount.setBalance(60.00);

Why is data hiding important to getters as well as setters? In fact, controlling access to getters is very important. Consider the problem of hiding information from people who should not be able to access the data. The most obvious application for this would be that of passwords. By making the balance attribute private, other objects can only retrieve the value of balance if the getter allows access. Consider the following code.

public double getBalance(String pwd){   if (pwd != "entry") {      System.out.println("Error!");   } else {      return balance;   }   return 0;}

By using the getter, we can control access to balance. In effect, the data is hidden.

Note: This error-handling example is used to illustrate the concept of data hiding. In practice, exceptions would be a better way to handle errors such as these.

Conclusion

In this column, we explored the basic concepts of encapsulation. Encapsulation means that an object contains both its attributes and behaviors. Data hiding is an important part of encapsulation and the access designation private ensures that attributes are truly hidden. A very good rule to live by is that ALL attributes should be designated as private. In practice, you should never use a public designation for any attribute. All access to attributes should be accomplished via getters and setters. Next month, we will expand this discussion and add functionality to our CheckingAccount class. For example, we might want to add deposit() and withdrawal() methods.

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

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date