Architecture & DesignPackaging Objects to Preserve Encapsulation

Packaging Objects to Preserve Encapsulation

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

This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and wants to understand the basic concepts before jumping into the code or someone who wants to understand the infrastructure behind an OOP language they are already using. Click here to start at the beginning of the series.


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 I will provide the code listings for all examples in this article. I have the SDK 1.4.0 loaded on my machine. I will also provide figures and the output (when appropriate) for these examples. See the previous articles in this series for detailed descriptions for compiling and running all the code examples in this series.


In the previous column we asked the question: does inheritance weaken encapsulation? We were able to prove with a code example that we are indeed able to do some things with inheritance that is questionable with regards to the encapsulation rule. Let’s do a quick review of the problem and the 2 important rules that appear to conflict with each other.


The Potential Problem Revisited


Whenever the interface/implementation paradigm is covered, we are really talking about encapsulation. The basic question is what in a class should be exposed and what should not be exposed. This encapsulation pertains equally to data and behavior. When talking about a class, the primary design decision revolves around encapsulating both the data and the behavior into a well-written class.


Inheritance is a mechanism by which a developer can design one class by inheriting the data and behavior of another class. For example, a Dog class can inherit data and behavior from a Mammal class. Thus, the designer can create data and behavior for all mammals and not have to duplicate this functionality for various mammals like dogs, cats, bears, etc.


Encapsulation is so fundamental to OO that enforcement of encapsulation is one of OO design’s cardinal rules. Inheritance is also considered one of the three primary OO concepts. However, in the previous column we found that, at least, in one way, inheritance actually breaks encapsulation!


Let’s deal with this problem by modifying the code that we were working on in the last article (we will start with a bit of a review).


A Code Example


In this example we created 3 classes, Mammal, Dog, and an application class called Packaging. The reason that we named the application Packaging was due to the fact that we are going to address our encapsulation/inheritance dilemma by using Java packaging. The Mammal class is shown in listing 1.

// Class Mammal
public class Mammal {
private String color;
public void growHair(){
System.out.println(“Hair Growing”);
}
}

Listing 1


In this class we have a private attribute called color and a public method called growHair(). To use the Mammal class we create an application called Packaging. This class is presented in listing 2.

//Class Packaging
public class Packaging {
public static void main (String args[]){

Mammal mammal = new Mammal();

mammal.growHair();
mammal.color = “blue”;

}

}


Listing 2


When we run this application we get the output in figure 1.



Figure 1


We can now test our encapsulation principle by attempting to have the application access the private attribute color as seen in listing 3.

//Class Packaging
public class Packaging {
public static void main (String args[]){

Mammal mammal = new Mammal();

mammal.growHair();
mammal.color = “blue”; // try to access private attribute

}

}


Listing 3


As we can see in figure 2, the compiler just won’t accept this.


Figure 2


This was all expected; however, let’s add inheritance to the mix by adding a class called Dog as seen in listing 4.

//Class Dog
public class Dog extends Mammal{
private int barkFrequency;
public void bark(){
System.out.println(“Dog Barking”);
}

}


Listing 4


Now we can start doing some interesting things. First, we can try to access Mammal’s attribute color from the Dog class – see listing 5.

//Class Dog
public class Dog extends Mammal{
private int barkFrequency;
public void bark(){
System.out.println(“Dog Barking”);
color = “blue”;
}

}


Listing 5


When we run this the compiler complains again as is seen in figure 3. Yet, at one level this appears incorrect. Since Dog Is-A Mammal, doesn’t the Dog contain all the attributes of Mammal? In short, is in not true that Dog inherits all the attributes and methods of Mammal?



Figure 3


This is an interesting design dilemma for object-oriented compiler builders. Here is the problem.


  • Inheritance dictates that a child class (subclass) inherits all the attributes and methods from a parent class (superclass).
  • Encapsulation dictates that one class must not have access to the (private) data of another class.

The way these rules are written it seems that, at least in one point, they may be mutually exclusive. How can this conundrum be addressed?


Since encapsulation is the primary object-oriented mandate we have no choice, we must make all attributes private. Making the attributes public is not an option. Thus, it appears that the use of inheritance may be severely limited. The important question is this: if a subclass does not have access to the attributes of its parent then we have a real design problem.


To allow subclasses to access the attributes of the parent, language designers have included the access modifier protected. There are actually 2 types of protected access, which we will explain in detail later in this column. Right now, let’s change the color access modifier to protected as seen in listing 6.

// Class Mammal
public class Mammal {
protected String color;
public void growHair(){
System.out.println(“Hair Growing”);
}
}

Listing 6


This change allows the code to compile and execute. By making color protected, Dog now has access to the attributes in Mammal – as we would expect. However, we now have another problem.


In listing 9 we put back the line to access Dog’s color attribute from the Packaging application.

//Class Packaging
public class Packaging {
public static void main (String args[]){
Mammal mammal = new Mammal();
mammal.growHair();
mammal.color = “blue”;
}
}

Listing 7


The problem is that this compilation process now works (see figure 4). In short, the application (class Packaging) can directly access Dog’s color attribute (via its parent Mammal). This is due to the fact that the protected modifier allows classes in the same package to access each others protected attributes. In this case we have created no packages so the Java compiler puts everything in a default package.



Note: if you create a Java file without defining a package at the top of the file, then the class is placed in the default package. The Sun online documentation suggests the following: “Generally speaking, the default package is only for small or temporary applications or when you are just beginning development. Otherwise, classes, enums and annotations belong in named packages.”


Regardless of what is going on, the application has direct access to an attribute in another class. Based on the rule of encapsulation, this is unacceptable. It appears that we have fixed the inheritance issue, but now we have broken encapsulation. What can we do?



Figure 4


We clearly have a dilemma. It appears that we have to make a decision as to whether to enforce inheritance or encapsulation. This example clearly shows the problem that inheritance poses to encapsulation. It is very helpful for a developer to fully understand the subtleties involved with the inner workings of various object-oriented constructs. By realizing what the issues are in this example, you can more fully grasp what inheritance and encapsulation are and how to make your designs more robust.

Packages Explained


One of the major features provided by the various Java releases was that of packages. Many structured programmers are familiar with the concept of code libraries. In some ways, packages can be thought of in the same way as code libraries – but they have many important differences. Perhaps the best way to describe a package is to actually show packages. This can be easily accomplished by inspecting a file called rt.jar.


Remember that a Java program is run inside of a virtual machine. The operative term here is virtual. When you run a Java application you do so by invoking the Java executable java.exe. After you install the SDK, you can find the java.exe file in the bin directory of the Java installation as seen in figure 5.

Figure 5.

The great thing about using a virtual machine, and specifically in the case of Java, is that your Java code can be run on any computer that has a virtual machine installed. The not-so-great thing is that there are computers that do not have a Java virtual machine installed. Thus, if you ship your application to a computer without a resident Java virtual machine, that computer will not be able to run your application. Another problem, and this can get somewhat sticky, is that the target computer may have a version of the SDK that is different from the version that you developed the application with. So, you may ask how we can safely ship an application if we have 2 unknowns:


  1. Don’t know if the target computer has Java installed.
  2. If it does, what version of the SDK does it have installed?

You may have guessed that the way to solve this problem is to actually ship the version of the JVM with your application. In this way, you know that the application will run with the same version of the JVM that you developed it with.


Some may find it odd that you would think of shipping a virtual machine with the application; however, this is a viable solution because you don’t need to ship the entire SDK.


You may have noticed in figure 5 that the SDK contains several directories (some installation may have different directory names):


  • bin
  • demo
  • include
  • jre
  • lib

The virtual machine java.exe is, as we have seen, in the bin directory. Many of the other directories include the tools you will need for development. However, all that you need to ship with the application is a subset of the SDK. The directory jre contains all this material you need to execute a Java application. The name jre stands for java runtime environment. The jre can be bundled with your application to ensure that it will run on a target machine.


Inside the jre directory is a file called rt.jar that we can use to illustrate our package example. Figure 6 shows the jre with the file rt.jar highlighted.



Figure 6


The extension jar stands for Java Archive. The great thing about these jar files is that they are built on zip technology (just like WinZip). In fact, you can actually use Winzip to open a jar file as seen in figure 7.



Figure 7


The only visible difference between a regular zip file and a jar file is the first file Manifest.mf which designates this as a Java jar file.


For example, when you want to develop Java Swing applications you must include the rt.jar file in your classpath and then import the Swing package that is included in the rt.jar file. You can easily find the Swing package inside the rt.jar file as seen in figure 8.



Figure 8


In figure 8, the class Boxview.class is selected. The file Boxview.class is part of the swing package and you can see that the packages are identified on the far right under the Path column.


Using packages is how Java bundles the development tools as well as the runtime environment. We can create our own packages to make development much more secure and productive. With this background on packages complete, we can now see how to create our own packages to solve the problem we encountered above when inheritance appears to break encapsulation.


Packaging Solution


Let’s create a design that will solve the problem we began this discussion with. Make the following changed to Mammal as seen in listing 8.

// Class Mammal
package Mammals; // add this line
public class Mammal {
protected String color;
public void growHair(){
System.out.println(“Hair Growing”);
}
}

Listing 8


The line in red creates a package called Mammal. Once this is created we can use and distribute this library and we can place the appropriate class files inside.


A Java package, to a certain degree, equates to a directory and its corresponding directory structure. Thus, to physically create our Mammals package we can simply create a sub-directory called Mammals. Inside your application directory, which here is called Packaging, create a subdirectory called Mammals and move the Mammal class inside this directory. Now let’s compile the project again and see what happens. Listing 9 shows how you need to adjust your compilation syntax. Note that you must include the directory path for the new package.

c:j2sdk1.4.2_05binjavac -classpath . ./Mammals/Mammal.java
c:j2sdk1.4.2_05binjavac -classpath . Dog.java

Listing 9


When we run this we see that the Dog class cannot find the Mammal source code to extend it – just as we expected since we moved the Mammal.java file. This can be seen in figure 9.



Figure 9


To address this problem we simply import the Mammal package in the Dog class as can be seen in the red line in listing 10.

//Class Dog
import Mammals.*;
public class Dog extends Mammal{
private int barkFrequency;
public void bark(){
System.out.println(“Dog Barking”);
color = “blue”;
}
}

Listing 10


Listing 11 shows the necessary added line in the Packaging class.

import Mammals.*; 
public class Packaging {
public static void main (String args[]){
Mammal mammal = new Mammal();
mammal.growHair();
mammal.color = “blue”;
}
}

Listing 11


This deals successfully with the problem of the Dog class finding the Mammal class; however, an error message appears when the Package class is compiled as can be seen in figure 10.



Figure 10


The error message states that the color attribute is protected and thus cannot be accessed from the Package application. Actually, this is exactly the behavior that we want! Now the color attribute can be successfully accessed from the Dog class (which is a valid inheritance relationship) – but not from the application (which we want to avoid like the plague per our encapsulation rule).


Protected Status


There is one more issue to explore in this discussion of packages. There is actually another type of access modifier – one we will call default protected, or package protected.


Take a look at listing 12 and the red line of code.

// Class Mammal
package Mammals;
public class Mammal {
String color;
public void growHair(){
System.out.println(“Hair Growing”);
}
}

Listing 12


Notice in this case that the color attribute appears to have no access modifier; however, it is provided a default access modifier. This default modifier is basically a protected modifier within the package only – it excludes subclasses. Thus, when we really do use the protected access modifier, this includes the inheritance hierarchy tree. Perhaps we can call this inheritance protected.


When we compile with this version of the Mammal class we get the results seen in figure 11.



Figure 11


This is because the Dog class is not in the Mammals package. We can solve this problem by including Dog in the Mammals package. Take a look at the red line of code in listing 13.

//Class Dog
package Mammals;
public class Dog extends Mammal{
private int barkFrequency;
public void bark(){
System.out.println(“Dog Barking”);
color = “blue”;
}
}

Listing 13


When we compile this we get the desired result. Dog is now part of the Mammals package so it has access to the attributes in Mammal; however, the application Packaging cannot access them.


Conclusion


At first, the encapsulation/inheritance dilemma covered in the past 2 articles may seem confusing. In fact, you may start to wonder if the whole paradigm is simply too intricate for its own good. Yet, once you finally do understand the issues behind these examples, they really do become interesting intellectual exercises and useful development techniques.


We have seen that at one level encapsulation and inheritance do conflict with each other. However, using Java packaging, you can successfully enforce both the inheritance rule and the encapsulation rule.


References


Gilbert, Stephen, and Bill McCarty: Object-Oriented Design in Java. The Waite Group, 1998.

Meyers, Scott: Effective C++. Addison-Wesley, 1992.

Tyma, Paul, Gabriel Torok and Troy Downing: Java Primer Plus. The Waite Group, 1996.

Ambler, Scott: The Object Primer. Cambridge University Press, 1998.

Jaworski, Jamie: Java 1.1 Developers Guide. Sams Publishing, 1997.

www.javasoft.com

About the Author


Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member 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. Besides the first edition of The Object-Oriented Thought Process, 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