1 of 24

Lecture 22

Multiple Inheritance, Mixins, Interfaces, Abstract Methods

Programming Languages

UW CSE 341 - Winter 2023

2 of 24

What next?

Done: classes for OOP essence: inheritance, overriding, dynamic dispatch

Now: what if we want to have more than just 1 superclass

  • Multiple inheritance: allow > 1 superclasses
    • Useful but has some problems (see C++)
  • Mixins: 1 superclass; > 1 method providers
    • Often a fine substitute for multiple inheritance and has fewer problems (see Scala traits, newer Java interfaces, Ruby modules, and a Racket programming idiom)
  • Traditional Java-style interfaces: allow > 1 types
    • Not needed in a dynamically typed language; fewer problems

3 of 24

Multiple Inheritance

  • If inheritance/overriding are so useful, why limit ourselves to one superclass?
    • Because the semantics is often awkward (will discuss)
    • Because it makes static type-checking harder (won’t discuss)
    • Because it makes efficient implementation harder (won’t discuss)
  • Is it useful? Sure! Examples:
    • Have color-point-3d% inherit from point-3d% and color-point%
      • Or maybe just from color%
    • Have student-athlete% inherit from student% and athlete%
  • With single inheritance, end up copying code or using non-OOP-style

4 of 24

Trees, dags, and diamonds

  • Note: subclass and superclass can be ambiguous
    • There are immediate subclasses, superclasses
    • And there are transitive subclasses, superclasses

  • Single inheritance: the class hierarchy is a tree
    • Nodes are classes
    • Parent is immediate superclass
    • Any number of children allowed

  • Multiple inheritance: the class hierarchy no longer a tree
    • Cycles still disallowed (a directed-acyclic graph)
    • If multiple paths show that X is a (transitive) superclass of Y, then we have diamonds

A

B

C

D

E

X

Y

V

W

Z

5 of 24

What could go wrong?

  • If V and Z both define a method m, what does Y inherit? What does super mean?
    • Directed resends useful (e.g., (super* Z m arg) )
  • What if X defines a method m that Z but not V overrides?
    • Still require directed resends?
  • If X instances have private state, should Y instances have one copy of the state or two?
    • Turns out each behavior can be desirable
    • So C++ has (at least) two forms of inheritance

X

Y

V

W

Z

6 of 24

color-point-3d% (??)

This is completely made up (doesn’t work) but shows what we might want

  • Similarly impossible in Java:

class ColorPoint3D extends Point3D, ColorPoint { … } //no!

; This is NOT Racket

(define color-point-3d%

(class (list color-point% point-3d%) ; multiple superclasses

(super-new color-point%)

(super-new point-3d%) ; wait, does this init point% again?

(inherit-from point-3d% dist-to-origin)

(define/override (->string)

(string-append (send this get-color)

":" (super point-3d% ->string)))))

7 of 24

Artist Cowboys

  • color-point-3d% would want one point% private state
  • artist-cowboy% would want two copies of person% private state
    • One pocket for each draw method

7

(define person% (class object% (super-new) …

(define/public (get-pocket-contents)))

(define artist% (class person% (super-new) …

(send this set-pocket-contents! (new brush% …))

(define/public (draw) …)))

(define cowboy% (class person% (super-new) …

(send this set-pocket-contents! (new gun% …))

(define/public (draw) …)))

(define artist-cowboy%

(class (list artist% cowboy%) …)) ; This is NOT Racket!

8 of 24

Mixins

  • While terminology varies, mixin a common advanced idea in OOP
  • A mixin is not a class -- it takes an existing class and creates a new subclass of it by adding a set of methods
    • Not a class, so not a legal argument to new
  • A class can be made out of:
    • One superclass [old idea]
    • The methods from any number of mixins [new idea]
    • Its own additions and changes [old idea]

9 of 24

Special support?

Various OOP languages have special constructs that enable mixins by one name or another

  • Ruby modules, Scala traits, Java v8 via interfaces with default methods
    • All these examples changed class definitions to have some way to “include” any number of mixins in addition to having one superclass

But do we need special support? Depends what else we have...

10 of 24

What is a mixin?

Rather than define “sets of methods” and then have class definitions “include” them, we can…

… define a mixin to “take in some class and produce a new class that also has some extra methods”

So it’s like a function over classes

Or in Racket it is a plain-old function that takes a class as an argument! :-)

  • Classes are first-class values
  • There is a mixin special form, but it is just a macro that expands to a function (see The Racket Guide if curious)

11 of 24

No new features needed

We said before classes are first-class values, so this is just an elegant idiom, where colorizer is just a function and parent% is just its argument

(define (colorizer parent%)

(class parent%

(super-new)

(init [color "black"])

(define current-color color)

(define/public (get-color) current-color)

(define/override (->string)

(string-append (get-color) ":" (super ->string)))))

(define color-point2% (colorizer point%))

(define color-point-3d2% (colorizer point-3d%))

12 of 24

A compelling example

A “killer app” for mixins is providing <, >, ==, !=, >=, <= in terms of a three-way compare provided by the superclass

  • Something many classes will want for convenience
  • Such classes will not want to “use their one superclass” to get it

See Racket function comparable and class time-of-day% for examples in the code

  • Use of interfaces in the example is idiomatic and helps prevent misuse, but isn’t fundamental (can be removed)
  • See code and The Racket Guide for what Racket interfaces are

13 of 24

Statically-Typed OOP

  • Now contrast multiple inheritance and mixins with Java-style interfaces

  • Java interfaces without default methods are about static typing

  • Since Racket does not have a type system, we will use Java code after quick introduction to static typing for class-based OOP…
    • Sound typing for OOP prevents “method missing” errors

14 of 24

Classes as Types

  • In Java/C#/etc. each class is also a type

  • Methods have types for arguments and result

  • If C is a (transitive) subclass of D, then C is a subtype of D
    • Type-checking allows subtype anywhere supertype allowed
    • So can pass instance of C to a method expecting instance of D

class A {

Object m1(Example e, String s) {…}

Integer m2(A foo, Boolean b, Integer i) {…}

}

15 of 24

Interfaces are (or were) Just Types

  • An interface is not a class; it is [er, used to be] only a type
    • Does not contain method definitions, only their signatures (types)
      • Unlike classes or mixins
      • (Changed in Java 8 so they can also be more like mixins)
    • Cannot use new on an interface
      • Like mixins

interface Example {

void m1(int x, int y);

Object m2(Example x, String y);

}

16 of 24

Implementing Interfaces

  • A class can explicitly implement any number of interfaces
    • For class to type-check, it must implement every method in the interface with the right type
    • Multiple interfaces no problem; just implement everything
  • If class type-checks, it is a subtype of the interface

class A implements Example {

public void m1(int x, int y) {…}

public Object m2(Example e, String s) {…}

}

class B implements Example {

public void m1(int pizza, int beer) {…}

public Object m2(Example e, String s) {…}

}

17 of 24

Purpose of interfaces

  • What interfaces are for:
    • “Caller can give any instance of any class implementing I
      • So callee can call methods in I regardless of class
    • So much more flexible type system than without interfaces
  • Interfaces aren’t needed in a dynamically typed language
    • Dynamic typing already much, much, much more flexible
    • Can pass any object anywhere and send any object any message
    • Usual static vs. dynamic typing trade-offs [next unit]

18 of 24

Abstract methods

Let’s now answer these questions:

  • What does a statically typed OOP language need to support “required overriding”?

  • How is this similar to higher-order functions?

  • Why does a language with multiple inheritance (e.g., C++) not need Java-style interfaces?

[Explaining Java’s abstract methods / C++’s pure virtual methods]

19 of 24

Required overriding

Often a class expects all subclasses to override some method(s)

    • The purpose of the superclass is to abstract common functionality across subclasses, but some non-common parts have no default
    • So instances of the superclass should not be made

20 of 24

Required overriding

A fine Racket approach:

    • Do not define must-override methods in superclass
    • Subclasses can add them
    • Creating instance of superclass lead to failed message sends

; do not create instances of abstract-foo%

; because baz is used but not defined

(define abstract-foo%

(class object%

(super-new)

(define/public (bar x)

(+ x (send this baz 37)))))

21 of 24

Static typing

  • In Java/C#/C++, this Racket approach fails type-checking
    • No method baz defined in superclass
    • One solution: provide error-causing implementation
  • In Racket: No reason to do this
  • In Java: This works, but much better style is make instantiation of abstract-foo% a type error

(define abstract-foo%

(class object%

(super-new)

(define/public (baz x) (error …))

(define/public (bar x)

(+ x (send this baz 37)))))

22 of 24

Abstract methods

Java/C#/C++ let superclass give signature (type) of method subclasses should provide

  • Called abstract methods or pure virtual methods
  • Cannot creates instances of classes with such methods
    • Catches error at compile-time
    • Indicates intent to code-reader
    • Does not make language more powerful

abstract class AbstractFoo {

abstract int baz(int x);

int bar(int x) { return x + this.baz(37); }

}

class ReadyToGo extends AbstractFoo {

int baz(int n) { return n * 2; }

}

23 of 24

Passing code to other code

  • Abstract methods and dynamic dispatch: An OOP way to have subclass “pass code” to other code in superclass

  • Higher-order functions: An FP way to have caller “pass code” to callee

let bar baz x = x + baz 37

let h x = … bar (fun n -> n*2) 12

abstract class AbstractFoo {

abstract int baz(int x);

int bar(int x) { return x + this.baz(37); }

}

class ReadyToGo extends AbstractFoo {

int baz(int n) { return n * 2; }

}

24 of 24

No interfaces in C++

  • If you have multiple inheritance and abstract methods, you do not also need Java-style interfaces

  • Replace each interface with a class with all abstract methods

  • Replace each “implements interface” with another superclass

So: Expect to see interfaces only in statically typed OOP without multiple inheritance