Lecture 15

Multiple Inheritance

In some OOP languages, such as Java and Ruby, a class can inherit from, at most, one other class. This is called single inheritance. But other languages, such as C++ and Python, let a class inherit from any number of other classes. This is known as multiple inheritance.

As an example of multiple inheritance, consider this Java-like pseudocode for a graphics program:

class Rectangle {
    // ...
}

class Clickable {
    // ...
}

class Button extends Rectangle, Clickable {
    // ...
}

This is just pseudocode: Java does not actually allow a class to extend two classes like this. But this examples shows that multiple inheritance might be useful in some cases. And, indeed, experience has shown that multiple inheritance is important and useful in object-oriented design, and that single-inheritance is generally too limiting.

However, multiple inheritance suffers from a couple of problems. One is that it is perceived by some programmers as unnecessarily complicated, and the cases where it is helpful don’t occur frequently enough to justify it.

Another problem is how to deal with a fundamental multiple inheritance problem. Consider the following example in Java-like pseudocode:

abstract class Person {
    abstract class String gender();
}

class Mom extends Person {
    String gender() { return "female"; }
}

class Dad extends Person {
    String gender() { return "male"; }
}

class Child extends Mom, Dad {
    // What is the body of Child's gender method?
}

The Child class inherits from both Mom and Dad. Since both Mom and Dad have a gender method, so too does Child. But the problem is what is the body of the Child gender method? There is a choice: Child could inherit the body of gender from Mom, or the body of gender from Dad, buy not both.

Many language designers see this as a fundamental problem that is due to inheriting implementation details. And so one solution, followed by Java, is to disallow multiple inheritance within classes (because classes allow implementations to be inherited), but to allow it with interfaces (where implementations cannot be inherited).

In languages that allow multiple inheritance with implementation, different solutions are used. For instance, in C++ both implementations of gender are inherited and the programmer explicitly says which one must be used. In contrast, the order in which the names of the classes are written decides what is inherited, so that class Child extends Mom, Dad is different than class Child extends Dad, Mom.

Abstract Classes and Interfaces

In class-based OOP (e.g. as in C++ or Java), it is often useful to write methods that don’t have a body. Often, such body-less methods are put in a so- called base class, the idea being that classes can extend this base class and provide their own implementation.

Here’s how you can use abstract classes in Java:

public abstract class Shape {
    public abstract double area();
    public abstract double perimeter();
}

public class Circle extends Shape {
    private double radius;

    public double area() { return 3.14 * radius * radius; }
    public double perimeter() { return 2 * 3.14 * radius; }
}

public class Square extends Shape {
    private double side;

    public double area() { return side * side; }
    public double perimeter() { return 4 * side; }
}

The Shape class cannot be instantiated because neither of its methods has a body. So its only purpose is to provide an “umbrella” type that specifies what method its subclasses will have.

Another way to do the same sort of thing in Java is to use an interface instead of a base class, e.g.:

public interface Shape {
    double area();
    double perimeter();
}

public class Circle implements Shape {
    private double radius;

    public double area() { return 3.14 * radius * radius; }
    public double perimeter() { return 2 * 3.14 * radius; }
}

public class Square implements Shape {
    private double side;

    public double area() { return side * side; }
    public double perimeter() { return 4 * side; } ;
}

The Circle and Square class are nearly the same as before, except now we use the implements keyword instead of extends.

A major difference between implements and extends is that a class can implement as many interfaces as it needs, while it can extend, at most, only one other class.

Another difference is that a Java interface can only list method signatures, and cannot provide any variables or bodies. In contrast, an abstract base class can provide common variables and also default implementations of methods.

Go also provides interfaces, e.g.:

type Shaper interface {
    area() float64
    perimeter() float64
}

type Circle struct {
    radius float64
}

func (c Circle) area() float64 { return 3.14 * c.radius * c.radius }
func (c Circle) perimeter() float64 { return 2 * 3.14 * c.radius }

type Square struct {
    side float64
}

func (s Square) area() float64 { return s.side * s.side }
func (s Square) perimeter() float64 { return 4 * s.side }

Unlike Java, Go does not require that you to explicit indicate that a type of objects implements a particular interface. Instead, Go just checks to see if the type implements all the required methods, and, if so, then the type is considered to implement he interface.

In practice, many programmers like interfaces because they are easy to use and quite flexible.

Mixins in Dart

The final OOP idea we will look at is the idea of a mixin. For concreteness, we will look specifically at mixins within the Dart language.

Suppose you are writing a video game, and you have these mixins:

class Position {
  num x, y;
}

class Velocity {
  num dx, dy;

  num speed() => sqrt(dx*dx + dy*dy);

  void stop() {
    dx = 0.0;
    dy = 0.0;
  }
}

In Dart, mixins are just classes with some restrictions:

  • They cannot have constructors.
  • They must extend the built-in class Object (which happens by default).
  • They cannot call super.

When defining a class in Dart, the with keyword lets us use a mixin:

class Cell extends Object with Position {

  Cell(init_x, init_y) {
    x = init_x;  // x and y are from Position
    y = init_y;
  }

  // ...
}

This causes the Position class to be mixed-in to Cell, i.e. the variables and methods in Position are put into Cell. Cell is not a subclass of Position, instead it just gets the variables and code from it.

Note the with Position in the class header. Although Dart does not allow it, it is useful to imagine it written like this:

class Cell extends (Object with Position)   // the brackets are not legal Dart
{
        // ...
}

The Position mixin can be thought of as being applied to the Object class.

Here’s another example:

    class Monster extends Object with Position, Velocity {
      String name;

      Monster(this.name) {

      }

      // ...
    }

/////////////////////////////////////////////////////

var m = new Monster("Kobold");
m.x = 0;
m.y = 0;
m.dx = 1.2;
m.dy = -3.4;
var s = m.speed();
m.stop();

The Monster class has all the variables and functions from Position and Velocity, but is just of type Monster.

A class can have as many mixins as it needs. However, in Dart it appears that if two or more mixins have a method with the same name, then the last method in the with clause is the one that is used. So care must be taken by the programmer to avoid naming conflicts.