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.