Week3

Interface Inheritance

Introduction to inheritance

Inheritance is a mechanism within the object-oriented programming paradigm that allows us to base an interface or class upon an existing interface or class and expanding the behavior or implementation. When we define an inheritance relationship between two classes or between two interfaces, there is usually a super-class or super-interface from which the behavior/implementation is inherited by one or more sub-class or sub-interfaces. The type of the sub-class or sub-interface will be regarded a specialization of the super class or interface. This way we can reuse common behavior or implementations of classes or interfaces which helps us achieve code reuse, or create code where we can easily switch between different types of specialized behavior which helps us to achieve polymorphism (similar to what we can achieve with interfaces). For example, if you build a general Animal class and define their characteristics, you may want to build subclasses for specific animals later. For example, think of the classes Dog, Horse, etc.. These classes need all the characteristics from Animal, and may add some other, more specific, methods or characteristics of these animals. Code and implementations that are common to all animals can be put in the Animal class, while code and implementations that are specific to the Dog and Horse can be put in those specific classes. Furthermore, objects of type Dog or Horse can also be used in places where the more general Animal type is needed, which would allow us to create a simulation for general Animal objects, and run different experiments with the specific behavior of Dog objects or Horse objects.

This week, you will learn more about inheritance. First we will discuss how inheritance works with interfaces, and then we move on to classes.

Interface inheritance

In last weeks material, an interface was introduce to be able to compare the outcomes of certain types of casino games based on some score/value. The interface looked like this:

public interface GameValue {
    int getValue();
}

The interface could then be used in different classes: PokerHand, BlackjackHand, etc. Objects of those different classes could be sorted for analysis by a single sorting algorithm that operated on the GameValue type, rather than having a separate function for each type. As an advantage, if we would introduce a new type of game, we can apply the analysis that was created earlier as soon as it implements the GameValue interface.

Since the bank has an advantage in some types of games, we want to be able to detect whether a value was obtained by the bank or someone else. We could add a method boolean fromBank(); to the GameValue interface, but then all our classes would need to add a second method, while some of the games may not even have a bank. Note that we do not explicitly write that this method is public, as interface methods cannot be private. You may write public, but please be consistent. We could create a second interface BankScore as follows:

public interface BankScore {
    boolean fromBank();
}

However, this approach gives us two separate types. On GameValue objects we can only call getValue() and on BankScore objects we can only call fromBank(). If the analysis information needs only one of the two pieces of information, this approach would work. However, if both pieces of information are needed, for example because you need to compute the average outcome that where influenced by the staff of the casino, you would need both types.

Using two separate interfaces we do not have a single type where we can use both the getValue() and fromBank() methods and we can choose only one type for (instance) variables and/or method and constructor arguments. This makes it difficult to access both the getValue() and fromBank() information at the same time.

To solve this issue, one idea could be to add int getValue() to the BankScore interface:

public interface BankScore {
    int getValue();
    boolean fromBank();
}

However, we can then still not use BankScore objects in our old sorting algorithm. BankScore then is still a separate type from GameValue.

To solve this problem, we let the interface BankScore inherit the interface GameValue with the extends keyword as follows:

public interface BankScore extends GameValue {
    boolean fromBank();
}

Now, an inheritance relationship between GameValue and BankScore is established and the type BankScore can be used as a GameValue and classes that implement BankScore must have both getValue() and fromBank() methods, as the getValue() method is inherited from GameValue.

We say that BankScore is a subtype of GameValue and that GameValue is a supertype of BankScore. A subtype can always do at least as much as its supertype. This terminology is based on set theory: the set of BankScore objects is a subset of the set of GameValue objects. The set of GameValue objects is a superset of the set of BankScore objects. Also supertype and subtype relations are transitive.

To summarize, when we establish an inheritance relationship between interface A and interface B with B extends A in the class header, the two following things happen:

  1. When a class implements interface B it needs to implement all methods that must be implemented when interface A is implemented, and in addition the methods define in interface B must be implemented as well.
  2. The type B is considered as a specialization of the A, so references to objects of type B can be assigned to variables of type A. However, there is no guarantee that every object of type A will also be an object of type B.

These relationships are transitive: if we have B extends A and C extends B, classes implementing C need to implement all methods from A, B and C, and object references of type C can be assigned to variables of both type A and type B.

Exercise

Test your knowledge

In this quiz, you can test your knowledge on the subjects covered in this chapter.

Explain how we can use interface inheritance and what the consequences of using it are.


Consider the following interfaces:

interface A {
    int getANumber();
}

interface B extends A {
    String getBString();
}

interface C extends B {
    double getCDouble();
}

interface D extends B {
    boolean getDBoolean();
}

Suppose we create some objects of these types in some way:

A aObj = /* some constructor */;
B bObj = /* some constructor */;
C cObj = /* some constructor */;
D dObj = /* some constructor */;

Determine for each of the following statements if they can be executed:

  • A otherA1 = bObj;
  • A otherA2 = cObj;
  • C otherC = aObj;
  • D otherD = bObj;
  • D otherD = cObj;
  • String bStr = aObj.getBString();
  • String bStr2 = cObj.getBString();
  • int aNum = bObj.getANumber();
  • int aNum2 = dObj.getANumber();
  • double cDouble = aObj.getCDouble();
  • boolean dBoolean = cObj.getDBoolean();
You have reached the end of this section! Continue to the next section: