Quantcast

P^i

Your Online Tech Magazine

Fri05242013

Last update12:17:26 PM

Back You are here: Home More Programming and Web Programming Java Guide: IS-A and HAS-A Relationships

Java Guide: IS-A and HAS-A Relationships



Java - IS-A and HAS-A RelationshipsIS-A and HAS-A Relationships

For now you need to be able to look at code and determine whether the code demonstrates an IS-A or HAS-A relationship. The rules are simple, so this should be one of the few areas where answering the questions correctly is almost a no-brainer.

IS-A

In OO, the concept of IS-A is based on class inheritance or interface implementation. IS-A is a way of saying, "this thing is a type of that thing." For example, a Mustang is a type of horse, so in OO terms we can say, "Mustang IS-A Horse." Subaru IS-A Car. Broccoli IS-A Vegetable (not a very fun one, but it still counts). You express the IS-A relationship in Java through the keywords extends (for class inheritance) and implements (for interface implementation).

public class Car {

// Cool Car code goes here

}

public class Subaru extends Car {

// Important Subaru-specific stuff goes here

// Don't forget Subaru inherits accessible Car members which

// can include both methods and variables.

}

A Car is a type of Vehicle, so the inheritance tree might start from the Vehicle

class as follows:

public class Vehicle { ... }

public class Car extends Vehicle { ... }

public class Subaru extends Car { ... }

In OO terms, you can say the following:

  • Vehicle is the superclass of Car.
  • Car is the subclass of Vehicle.
  • Car is the superclass of Subaru.
  • Subaru is the subclass of Vehicle.
  • Car inherits from Vehicle.
  • Subaru inherits from both Vehicle and Car.
  • Subaru is derived from Car.
  • Car is derived from Vehicle.
  • Subaru is derived from Vehicle.
  • Subaru is a subtype of both Vehicle and Car.

Returning to our IS-A relationship, the following statements are true:

"Car extends Vehicle" means "Car IS-A Vehicle."

"Subaru extends Car" means "Subaru IS-A Car."

And we can also say:

"Subaru IS-A Vehicle" because a class is said to be "a type of" anything further up in its inheritance tree. If the expression (Foo instanceof Bar) is true, then class Foo IS-A Bar, even if Foo doesn't directly extend Bar, but instead extends some other class that is a subclass of Bar. Figure 2-2 illustrates the inheritance tree for Vehicle, Car, and Subaru. The arrows move from the subclass to the superclass. In other words, a class' arrow points toward the class from which it extends.

Inheritance tree for vehicle, Car, Subaru

 


HAS-A

HAS-A relationships are based on usage, rather than inheritance. In other words, class A HAS-A B if code in class A has a reference to an instance of class B. For example, you can say the following, A Horse IS-A Animal. A Horse HAS-A Halter. The code might look like this:

public class Animal { }

public class Horse extends Animal {

private Halter myHalter;

}

In the preceding code, the Horse class has an instance variable of type Halter, so you can say that "Horse HAS-A Halter." In other words, Horse has a reference to a Halter. Horse code can use that Halter reference to invoke methods on the Halter, and get Halter behavior without having Halter-related code (methods) in the Horse class itself. Figure 2-3 illustrates the HAS-A relationship between Horse and Halter.

HAS-A relationship between Harse and Halter

HAS-A relationships allow you to design classes that follow good OO practices by not having monolithic classes that do a gazillion different things. Classes (and their resulting objects) should be specialists. As our friend Andrew says, "specialized classes can actually help reduce bugs." The more specialized the class, the more likely it is that you can reuse the class in other applications. If you put all the Halter-related code directly into the Horse class, you'll end up duplicating code in the Cow class, UnpaidIntern class, and any other class that might need Halter behavior. By keeping the Halter code in a separate, specialized Halter class, you have the chance to reuse the Halter class in multiple applications. Users of the Horse class (that is, code that calls methods on a Horse instance), think that the Horse class has Halter behavior. The Horse class might have a tie(LeadRope rope) method, for example. Users of the Horse class should never have to know that when they invoke the tie() method, the Horse object turns around and delegates the call to its Halter class by invoking myHalter.tie(rope).

The scenario just described might look like this:

public class Horse extends Animal {

private Halter myHalter;

public void tie(LeadRope rope) {

myHalter.tie(rope); // Delegate tie behavior to the

// Halter object

}

}

public class Halter {

public void tie(LeadRope aRope) {

// Do the actual tie work here

}

}

In OO, we don't want callers to worry about which class or which object is actually doing the real work. To make that happen, the Horse class hides implementation details from Horse users. Horse users ask the Horse object to do things (in this case, tie itself up), and the Horse will either do it or, as in this example, ask something else to do it. To the caller, though, it always appears that the Horse object takes care of itself. Users of a Horse should not even need to know that there is such a thing as a Halter class.


Polymorphism

Remember, any Java object that can pass more than one IS-A test can be considered polymorphic. Other than objects of type Object, all Java objects are polymorphic in that they pass the IS-A test for their own type and for class Object. Remember that the only way to access an object is through a reference variable, and there are a few key things to remember about references:

  • A reference variable can be of only one type, and once declared, that type can never be changed (although the object it references can change).
  • A reference is a variable, so it can be reassigned to other objects, (unless the reference is declared final).
  • A reference variable's type determines the methods that can be invoked on the object the variable is referencing.
  • A reference variable can refer to any object of the same type as the declared reference, or—this is the big one—it can refer to any subtype of the declared type!
  • A reference variable can be declared as a class type or an interface type. If the variable is declared as an interface type, it can reference any object of any class that implements the interface.

Earlier we created a GameShape class that was extended by two other classes, PlayerPiece and TilePiece. Now imagine you want to animate some of the shapes on the game board. But not all shapes can be animatable, so what do you do with class inheritance? Could we create a class with an animate() method, and have only some of the GameShape subclasses inherit from that class? If we can, then we could have PlayerPiece, for example, extend both the GameShape class and Animatable class, while the TilePiece would extend only GameShape. But no, this won't work! Java supports only single inheritance! That means a class can have only one immediate superclass. In other words, if PlayerPiece is a class, there is no way to say something like this:

class PlayerPiece extends GameShape, Animatable { // NO!

// more code

}

A class cannot extend more than one class. That means one parent per class. A class can have multiple ancestors, however, since class B could extend class A, and class C could extend class B, and so on. So any given class might have multiple classes up its inheritance tree, but that's not the same as saying a class directly extends two classes.

Some languages (like C++) allow a class to extend more than one other class. This capability is known as "multiple inheritance." The reason that Java's creators chose not to allow multiple inheritance is that it can become quite messy. In a nutshell, the problem is that if a class extended two other classes, and both superclasses had, say, a doStuff() method, which version of doStuff() would the subclass inherit? This issue can lead to a scenario known as the "Deadly Diamond of Death," because of the shape of the class diagram that can be created in a multiple inheritance design. The diamond is formed when classes B and C both extend A, and both B and C inherit a method from A. If class D extends both B and C, and both B and C have overridden the method in A, class D has, in theory, inherited two different implementations of the same method. Drawn as a class diagram, the shape of the four classes looks like a diamond.

So if that doesn't work, what else could you do? You could simply put the animate() code in GameShape, and then disable the method in classes that can't be animated. But that's a bad design choice for many reasons, including it's more errorprone, it makes the GameShape class less cohesive (more on cohesion in a minute), and it means the GameShape API "advertises" that all shapes can be animated, when in fact that's not true since only some of the GameShape subclasses will be able to successfully run the animate() method.

So what else could you do? You already know the answer—create an Animatable interface, and have only the GameShape subclasses that can be animated implement that interface. Here's the interface:

public interface Animatable {

public void animate();

}

And here's the modified PlayerPiece class that implements the interface:

class PlayerPiece extends GameShape implements Animatable {

public void movePiece() {

System.out.println("moving game piece");

}

public void animate() {

System.out.println("animating...");

}

// more code

}

So now we have a PlayerPiece that passes the IS-A test for both the GameShape class and the Animatable interface. That means a PlayerPiece can be treated polymorphically as one of four things at any given time, depending on the declared type of the reference variable:

  • An Object (since any object inherits from Object)
  • A GameShape (since PlayerPiece extends GameShape)
  • A PlayerPiece (since that's what it really is)
  • An Animatable (since PlayerPiece implements Animatable)

The following are all legal declarations. Look closely:

PlayerPiece player = new PlayerPiece();

Object o = player;

GameShape shape = player;

Animatable mover = player;

There's only one object here—an instance of type PlayerPiece—but there are four different types of reference variables, all referring to that one object on the heap. Pop quiz: which of the preceding reference variables can invoke the display() method? Hint: only two of the four declarations can be used to invoke the display() method. Remember that method invocations allowed by the compiler are based solely on the declared type of the reference, regardless of the object type. So looking at the four reference types again—Object, GameShape, PlayerPiece, and Animatable— which of these four types know about the display() method? You guessed it—both the GameShape class and the PlayerPiece class are known (by the compiler) to have a display() method, so either of those reference types can be used to invoke display(). Remember that to the compiler, a PlayerPiece IS-A GameShape, so the compiler says, "I see that the declared type is PlayerPiece, and since PlayerPiece extends GameShape, that means PlayerPiece inherited the display() method. Therefore, PlayerPiece can be used to invoke the display() method."

Which methods can be invoked when the PlayerPiece object is being referred to using a reference declared as type Animatable? Only the animate() method. Of course the cool thing here is that any class from any  nheritance tree can also implement Animatable, so that means if you have a method with an argument declared as type Animatable, you can pass in PlayerPiece objects, SpinningLogo objects, and anything else that's an instance of a class that implements Animatable. And you can use that parameter (of type Animatable) to invoke the animate() method, but not the display() method (which it might not even have), or anything other than what is known to the compiler based on the reference type. The compiler always knows, though, that you can invoke the methods of class Object on any object, so those are safe to call regardless of the reference—class or interface— used to refer to the object. We've left out one big part of all this, which is that even though the compiler only knows about the declared reference type, the JVM at runtime knows what the object really is. And that means that even if the PlayerPiece object's display() method is called using a GameShape reference variable, if the PlayerPiece overrides the display() method, the JVM will invoke the PlayerPiece version! The JVM looks at the real object at the other end of the reference, "sees" that it has overridden the method of the declared reference variable type, and invokes the method of the object's actual class. But one other thing to keep in mind:

Polymorphic method invocations apply only to instance methods. You can always refer to an object with a more general reference variable type (a superclass or interface), but at runtime, the ONLY things that are dynamically selected based on the actual object (rather than the reference type) are instance methods. Not static methods. Not variables. Only overridden instance methods are dynamically invoked based on the real object's type.

Since this definition depends on a clear understanding of overriding, and the distinction between static methods and instance methods, we'll cover those next.








blog comments powered by Disqus