Encapsulation vs. Inheritance
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 applicationsI 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. Click here to start at the beginning of the series.
In the last column, you took a look inside the bytecodes produced by constructors. Using the javap tool, provided by the SDK, you were able to see that, even if the programmer does not include a constructor in the code, the compiler will provide a default constructor automatically.
Constructors provide a good mechanism to support encapsulation. By designing proper constructors, you can properly initialize your encapsulated data. There is really no point to encapsulating your data if you fail to place your system in a safe state. Encapsulation is perhaps the most important rule that an object-oriented designer/programmer must adhere to. However, is Encapsulation as sacred as many software development pundits profess? In this column, you will explore this question, which turns out to be quite interesting.
As mentioned earlier in this series, one of the most powerful attributes of O-O programming is code reuse. Procedural programming provides code reuse to a certain degreeyou can write a procedure and then use it as many times as you want. However, O-O programming goes an important step further, allowing you to define relationships between classes that facilitate not only code reuse, but also better overall design, by organizing classes and factoring in commonalties of various classes. Inheritance is the primary means of providing this functionality.
Inheritance allows a class to inherit the attributes and methods of another class. This allows you to create brand new classes by abstracting out common attributes and behaviors.
One of the major design issues in O-O programming is to factor out commonality of the various classes. For example, say you have a Dog class and a Cat class, and each will have an attribute for eye color. In a procedural model, the code for Dog and Cat would each contain this attribute. In an O-O design, the color attribute can be abstracted up to a class called Mammalalong with any other common attributes and methods. In this case, both Dog and Cat inherit from the Mammal class, as shown in Figure 1.
Figure 1: Mammal hierarchy.
The Dog and Cat classes both inherit from Mammal. This means that a Dog class actually has the following attributes:
eyeColor // inherited from Mammal barkFrequency // defined only for Dogs
In the same vein, Dog object has the following methods:
GetEyeColor // inherited from Mammal Bark // defined only for Dogs
When the Dog or the Cat object is instantiated, it contains everything in its class, as well as everything from the parent class. Thus, Dog has all the properties of its class definition, as well as the properties inherited from the Mammal class.
One of the primary advantages of using objects is that the object need not reveal all of its attributes and behaviors. In good O-O design (at least what is generally accepted as good), an object should only reveal the interfaces needed to interact with it. Details not pertinent to the use of the object should be hidden from other objects. This is called encapsulation.
As discussed earlier in this series, the interface is the fundamental means of communication between objects. Each class design specifies the interfaces for the proper instantiation and operation of objects. Any behavior that the object provides must be invoked by a message sent using one of the provided interfaces. The interface should completely describe how users of the class interact with the class. In Java, the methods that are part of the interface are designated as public; everything else is part of the private implementation.
The Encapsulation Rule
Whenever the interface/implementation paradigm is covered, you 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.
Stephen Gilbert and Bill McCarty define encapsulation as "the process of packaging your program, dividing each of its classes into two distinct parts: the interface and the implementation." This is the message that has been presented over and over again in this series.
But what does encapsulation have to do with inheritance, and how does it apply with regard to this series? Encapsulation is so crucial to O-O development that it is one of the generally accepted object-oriented design's cardinal rules. Yet, Inheritance is also considered one of the three primary O-O concepts. However, in one way, inheritance actually breaks encapsulation! How can this be? Is it possible that two of the three primary concepts of O-O are incompatible with each other? Well, let's explore this possibility.
Recall that there are three criteria that determine whether or not a language is object-oriented: encapsulation, inheritance, and polymorphism. To be considered a true object-oriented language, the language must support all three of these concepts.