1 Advanced Abstraction Mechanisms Mixins Traits Delegation.

Post on 22-Dec-2015

220 views 3 download

Tags:

Transcript of 1 Advanced Abstraction Mechanisms Mixins Traits Delegation.

1

Advanced Abstraction Advanced Abstraction MechanismsMechanisms

Mixins Traits Delegation

2

Int = Printer + NumberInt = Printer + Numberstruct Number { int n; void setValue(int v) { n = v; } virtual int getValue() { return n; }}; struct Printer { virtual int getValue() { return 0; } void print() { cout << getValue() << endl; }}; struct Int : Printer, Number { };

int main(int, char**) { Int* n = new Int(); n->setValue(5); n->print(); // Output: "0" ?! }

3

Discussion #1Discussion #1 Printer is not a subclass of Number

Number::getValue() does not override Printer::getValue()

We want Number::getValue() to win But, when we look at the code we see that the two

methods are symmetric

4

Int Class, 2Int Class, 2ndnd Attempt Attemptstruct Number { int n; void setValue(int v) { n = v; } virtual int getValue() { return n; }}; struct Printer { virtual int getValue() = 0; // Pure virtual function void print() { cout << getValue() << endl; }}; struct Int : Printer, Number { };

int main(int, char**) { Int* n = new Int(); // “Cannot allocate an object of type Int” n->setValue(5); n->print(); }

5

Discussion #2Discussion #2 We tried to break the symmetry

Printer::getValue() is now abstract (Pure-virtual in C++ jargon)

This does not solve the problem The only implementation of getValue() is in Number

Which is not a sub-class of Printer => There's no definition that overrides Printer::getValue() => in class Int, Printer::getValue() is unimplemented => class Int is abstract

Cannot be instantiated

633rdrd Attempt: Mazal Tov Attempt: Mazal Tovstruct Number { int n; void setValue(int v) { n = v; } virtual int getValue() { return n; }}; struct Printer { virtual int getValue() = 0; void print() { cout << getValue() << endl; }}; struct Int : Printer, Number { int getValue() { return Number::getValue(); }};

int main(int, char**) { Int* n = new Int(); n->setValue(5); n->print(); }

7

Discussion #3Discussion #3 Pros:

We have a winner!

Cons: We had to do extra work to “glue” the two classes We have to redo it for every similar method

Summary: The problem occurs when.... We have two classes that we want to inherit from One of the superclasses depends on something that is

provided by the other

Summary: The problem does not occur when.... We have two classes that we want to inherit from No dependency between the superclasses

8

MixinsMixins A subclassing mechanism

Based on the copying principle

In normal inheritance we specify two things: S: Superclass D: Definition to copy == Delta to add to S.

Mixins: two basic operations A mixin definition: specify D

Also specify expectations from S

Instantiation: Combine a delta D with a super class S Results in a new class that extends S with the D

Result: Define D once, compose it many times Each time with a different S

9

Mixin: C++Mixin: C++struct Number { int n; void setValue(int v) { n = v; } virtual int getValue() { return n; }}; template<typename S> // Define the mixinstruct Printer : S { void print() { cout << getValue() << endl; }}; struct Int : Printer<Number> // Instantiate the mixin{ };

int main(int, char**) { Int* n = new Int(); n->setValue(5); n->print(); }

10

Properties of MixinsProperties of Mixins Separate the D from the S

That's why we have two operations: definition & instantiation The same D can be composed many times

Each time with a different S

Linearization: We have a clear winner The resulting class inherits from D which inherits from S No extra work for gluing the two classes

Expectations of D Expressed as calls to non-existing methods

Drawbacks of the mixin idiom we just saw (C++): The (uninstantiated) mixin class is not a type

We cannot up cast to D D is compiled only when instantiated

11

C++: A Mixin is not a TypeC++: A Mixin is not a Type

struct Number { ... }; template<typename S> struct Printer : S { ... } struct Int : Printer<Number> { };

int main(int, char**) { Printer<Number>* pn = new Int(); Printer* p = new Int(); // Compiler error!}

12

Workaround for the Workaround for the Mixin-is-not-a-type problemMixin-is-not-a-type problem

struct Number { int getValue() { return 10; } };

struct Printable { virtual void print() = 0; };

template<typename S>struct Printer : S, Printable // Inherit from S, but also from Printable // Thus we can use Printable as a type { void print() { cout << getValue() << endl; }};

struct Int : Printer<Number> { };

int main(int, char**) { Printable* p = new Int(); p->print();}

13

Jam: Java with MixinsJam: Java with Mixinspublic class Number { private int n; void setValue(int v) { n = v; } int getValue() { return n; }}; mixin Printer { inherited public int getValue(); public void print() { System.out.println(getValue()); }} class Int = Printer extends Number { }

public void f() { Int n = new Int(); n.setValue(5); Printer p = n; }

14

Discussion: Jam MixinsDiscussion: Jam Mixins All properties of C++ mixins

Separate the D from the S Linearization => Clear winner

Expectations are declared explicitly Using the inherited keyword

Drawbacks are solved: The (uninstantiated) mixin class is a type D is compiled when defined (becomes an interface)

15

Linearization is Not So Great #1Linearization is Not So Great #1 Total ordering

C extends B extends A We want C to offer B.f() and A.g() ?!

class A { public int f() { return 0; } public int g() { return 1; }}

mixin B { public int f() { return 2; } public int g() { return 3; }}

class C = B extends A { }

16

Linearization is Not So Great #2Linearization is Not So Great #2 Fragile inheritance: Adding a method to a mixin (f')

May silently override an inherited method (f) The resulting class prefers the behaviour of f

17

Traits:Traits:Flattening instead of LinearizationFlattening instead of Linearization

Trait = A composable unit of behaviour

No fields

Provides some methods (with behaviour)

Requires other methods (abstract)

Serves as a type

When composing traits, if a method has more than one implementation it becomes abstract

18Java with TraitsJava with Traitstrait T1 { abstract void add(int v); void inc() { add(1); }}

trait T2 { abstract int getValue(); abstract void setValue(int v); void add(int v) { setValue(getValue() + v); }} class Int uses T1, T2 { int n; int getValue() { return n; } void setValue(int v) { n = v; }}

T1 t1 = new Int(); // A trait is also typet1.add(3);

19Conflict ResolutionConflict Resolutiontrait T1 { void add(int v) { while(--v >= 0) inc(); } void inc() { add(1); }}

trait T2 { abstract int getValue(); abstract void setValue(int v); void add(int v) { setValue(getValue() + v); }} class Int uses T1, T2 { // Int can also extend a “normal” superclass int n; int getValue() { return n; } void setValue(int v) { n = v; } void add() { T2.this.add(); } // Resolve the conflict // Otherwise, a compiler error // when compiling Int}

20

Class Composition vs. Object CompositionClass Composition vs. Object Composition So far we saw mechanisms for creating new classes

from existing ones AKA: Class composition

Q: Can we extend existing objects? E.g., add new methods, fields to an object?

A: In Dynamically typed languages - Yes Especially in Javascript, Ruby In LST – partially

What about statically typed languages?

21

Object Composition - MotivationObject Composition - Motivationpackage java.awt;public class Color { public final Color RED = new Color(255,0,0); public final Color GREEN = new Color(0,255,0);

public Color(int r, int g, int b) { ... } public int getRed() { ... } ...}

public class MyColor extends Color { public boolean isRedder(Color c) { return getRed() > c.getRed(); }}

// We want to evaluate: Color.RED.isRedder(Color.GREEN)

// But, we can't because we cannot change the initialization of Color.RED

22

Object Composition – Manual DelegationObject Composition – Manual Delegation

public class MyColor extends Color { private Color inner; public MyColor(Color inner) { this.inner = inner; }

public int getRed() { return inner.getRed(); } public int getGreen() { return inner.getGreen(); } public String toString() { return "MyColor: " + inner.toString(); } ...

public boolean isRedder(Color c) { return getRed() > c.getRed(); }}

MyColor mc = new MyColor(Color.RED);mc.isRedder(Color.GREEN)

23

Drawbacks of Manual DelegationDrawbacks of Manual Delegation A lot of typing

Must redefine every method of the inner object

Silent errors: If a new method is added We must remember to add a forwarding implementation at

the outer class

We have two sets of fields In the outer and the inner object But we only use the inner one

The “overriding” semantics is only from the outside If the inner object sends a message to itself the inner

method is dispatched

(Some of these problems are solved if the inner class is an interface)

24Overriding is PartialOverriding is Partialinterface Value { int get(); void print();}

class ValueImpl implements Value { int get() { return 5; } void print() { System.out.println(get()); }}

class Outer implements Value { private Value inner; Outer(Value inner) { this.inner = inner; } int get() { return 10; } void print() { inner.print(); }}

Value v = new Outer(new ValueImpl()):v.get(); // Result: 10 :)v.print(); // Output: "5" :(

25Lava: ImplementationLava: Implementation

class Outer { private delegation Value inner; Outer(Value v) { inner = v; }

int get() { return 10; }

// Compiler generated code: void print() { inner.print() // But pass this (instead of inner) as

// the this parameter of the invoked method }}

26Delegation is Far From Being PerfectDelegation is Far From Being Perfect

interface I { void f(); void g(); }

class A implements I { void f() { g(); h(); } void g() { } void h() { }}

class B implement I { void f() { } void g() { }}

class C { private delegation I inner = new A(); void g() { inner = new B(); }}

new C().f();