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.