Architecture & DesignObject Relationships

Object Relationships

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

Object Relationships

This installment of the Object-Oriented Thought Process completes the set of three articles dedicated to what I consider to be the four most important object-oriented concepts:

  • Encapsulation
  • Inheritance
  • Polymorphism
  • Composition

These are the concepts that must be mastered before we can explore more detailed object-oriented technologies. In future columns, we will focus more on object-oriented design techniques and implementation strategies. Because encapsulation was covered in the two previous articles, we will start here with the topic of inheritance.

Inheritance

As we already have discussed, one of the most powerful attributes of OO programming is code reuse. Procedural programming provides code reuse to a certain degree—you can write a procedure and then use it as many times as you want. However, OO programming goes an important step further by allowing you to define relationships between classes. These relationships facilitate code reuse as well as better overall design, by organizing classes and factoring in commonalties of various classes. Inheritance is a 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 OO 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 OO design, the color attribute could be moved up to a class called Mammal along 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, the 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.

Superclasses and Subclasses

The superclass, or parent class, contains all the attributes and behaviors that are common to classes that inherit from it. For example, in the case of the Mammal class, all mammals have similar attributes such as eyeColor and hairColor, as well as behaviors such as generateInternalHeat and growHair. All mammals have these attributes and behaviors, so it is not necessary to duplicate them down the inheritance tree for each type of mammal. Thus, the Dog and Cat classes inherit all those common attributes and behaviors from the Mammal class. The Mammal class is considered the superclass of the Dog and the Cat subclasses, or child classes.

Inheritance provides a rich set of design advantages. When you’re designing a Cat class, the Mammal class provides much of the functionality needed. By inheriting from the Mammal object, Cat already has all the attributes and behaviors that make it a true mammal. To make it more specifically a cat type of mammal, the Cat class must include any attributes or behaviors that pertain solely to a cat.

Abstraction

An inheritance tree can grow quite large. When the Mammal and Cat classes are complete, other mammals, such as dogs (or lions, tigers, and bears), can be added quite easily. The Cat class can also be a superclass to other classes. For example, it might be necessary to abstract the Cat class further, to provide classes for Persian cats, Siamese cats, and so on. Just as with Cat, the Dog class can be the parent for GermanShepherd and Poodle (see Figure 2). The power of inheritance lies in its abstraction and organization techniques.

Figure 02: Mammal UML diagram.

Note that the classes GermanShepherd and Poodle both inherit from Dog each contains only a single method. However, because they inherit from Dog, they also inherit from Mammal. Thus, the GermanShepherd and Poodle classes contain all the attributes and methods included in Dog and Mammal, as well as their own (see Figure 3).

Figure 03: Mammal hierarchy.

Is-a Relationships

Consider a Shape example where Circle, Square, and Star all inherit directly from Shape. This relationship is often referred to as an is-a relationship because a circle is a shape and Square is a shape. When a subclass inherits from a superclass, it can do anything that the superclass can do. Thus, Circle, Square, and Star are all extensions of Shape.

In Figure 4, the name on each of the objects represents the Draw method for the Circle, Star, and Square objects, respectively. When we design this Shape system it would be very helpful to standardize how we use the various shapes. Thus, we could decide that if we want to draw a shape, no matter what shape, we will invoke a method called draw. If we adhere to this decision, whenever we want to draw a shape, only the Draw method needs to be called, regardless of what the shape is. Here lies the fundamental concept of polymorphism it is the individual object’s responsibility, be it a Circle, Star, or Square, to draw itself.

Figure 4 The shape hierarchy.

Polymorphism

Polymorphism literally means many shapes. Although polymorphism is tightly coupled to inheritance, it is often cited separately as one of the most powerful advantages to object-oriented technologies. When a message is sent to an object, the object must have a method defined to respond to that message. In an inheritance hierarchy, all subclasses inherit the interfaces from their superclass. However, because each subclass is a separate entity, each might require a separate response to the same message. For example, consider the Shape class and the behavior called Draw. When you tell somebody to draw a shape, the first question he asks is “What shape?” He cannot draw a shape, as it is an abstract concept (in fact, the Draw() method in the Shape code following contains no implementation). You must specify a concrete shape. To do this, you provide the actual implementation in Circle. Even though Shape has a Draw method, Circle overrides this method and provides its own Draw() method. Overriding basically means replacing an implementation of a parent with one from a child.

For example, suppose you have an array of three shapes Circle, Square, and Star. Even though you treat them all as Shape objects, and send a Draw message to each Shape object, the end result is different for each because Circle, Square, and Star provide the actual implementations. In short, each class is able to respond differently to the same Draw method and draw itself. This is what is meant by polymorphism.

Consider the following Shape class:

public abstract class Shape{
   private double area;
   public abstract double getArea();
}

The Shape class has an attribute called area that holds the value for the area of the shape. The method getArea() includes an identifier called abstract. When a method is defined as abstract, a subclass must provide the implementation for this method; in this case, Shape requires subclasses to provide a getArea() implementation. Now, let’s create a class called Circle that inherits from Shape (the extends keyword signifies that Circle inherits from Shape):

public class Circle extends Shape{
   double radius;
   public Circle(double r) {
      radius = r;
   }
   public double getArea() {
      area = 3.14*(radius*radius);
      return (area);
   };
}

We introduce a new concept here called a constructor. The Circle class has a method with the same name, Circle. When the names are the same and no return type is provided, the method is a special method, called a constructor. Consider a constructor the entry point for the class, where the object is constructed; the constructor is a good place to perform initializations.

The Circle constructor accepts a single parameter, representing the radius, and assigns it to the radius attribute of the Circle class.

The Circle class also provides the implementation for the getArea method, originally defined as abstract in the Shape class.

We can create a similar class, called Rectangle:

public class Rectangle extends Shape{
   double length;
   double width;
   public Rectangle(double l, double w){
      length = l;
      width = w;
   }
   public double getArea() {
      area = length*width;
      return (area);
   };
}

Now, we can create any number of Rectangles, Circles, and so forth and invoke their getArea() method because we know that all Rectangles and Circles inherit from Shape and all Shape classes have a getArea() method. If a subclass inherits an abstract method from a superclass, it must provide a concrete implementation of that method, or else it will be an abstract class itself (see Figure 5 for a UML diagram).

Figure 5: Shape UML diagram.

Thus, we can instantiate the Shape classes in this way:

Circle circle       = new Circle(5);
Rectangle rectangle = new Rectangle(4,5);

Then, using a construct such as a stack, we can add these Shape classes to the stack:

stack.push(circle);
stack.push(rectangle);

Now comes the fun part. We can empty the stack, and we do not have to care about what kind of Shape classes are in it:

while ( !stack.empty()) {
   Shape shape = (Shape) stack.pop();
   System.out.println ("Area = " + shape.getArea());
}

In reality, we are sending the same message to all the shapes:

shape.getArea()

However, the actual behavior that takes place depends on the type of shape. For example, Circle will calculate the area for a circle, and Rectangle will calculate the area of a rectangle. In effect (and here is the key concept), we are sending a message to the Shape classes and experiencing different behavior depending on what subclass of Shape is being used.

Composition

It is natural to think of objects as containing other objects. A television set contains a tuner and video display. A computer contains video cards, keyboards, and drives. Although the computer can be considered an object unto itself, the drive is also considered a valid object. In fact, you could open up the computer and remove the drive and hold it in your hand. Both the computer and the drive are considered objects. It is just that the computer contains other objects such as drives.

In this way, objects are often built, or composed, from other objects: This is composition.

Has-a Relationships

Although an inheritance relationship is considered an Is-a relationship for reasons already discussed, a composition relationship is termed a Has-a relationship. Using the example in the previous section, a television Has-a tuner and Has-a video display. A television is obviously not a tuner, so there is no inheritance relationship. In the same vein, a computer Has-a video card, Has-a keyboard, and Has-a disk drive. The topics of inheritance, composition, and how they relate to each other is covered in great detail in a later column.

Conclusion

There is a lot to cover when discussing OO technologies. However, you should leave this set of three columns with a good understanding of the following topics:

  • Encapsulation: Encapsulating the data and behavior into a single object is of primary importance in OO development. A single object contains both its data and behaviors and can hide what it wants from other objects.
  • Inheritance: A class can inherit from another class and take advantage of the attributes and methods defined by the superclass.
  • Polymorphism: Polymorphism means that similar objects can respond to the same message in different manners. For example, you might have a system with many shapes. However, a circle, a square, and a star are each drawn differently. By using polymorphism, you can send each of these shapes the same message (for example, Draw), and each shape is responsible for drawing itself.
  • Composition: Composition means that an object is built from other objects.

We will explore these concepts and more in future columns.

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.

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories