3db2290a2a28ab25990827d156073ac4.ppt
- Количество слайдов: 26
1 Advanced Abstraction Mechanisms Mixins Traits Delegation
Int = Printer + Number struct Number { int n; void set. Value(int v) { n = v; } virtual int get. Value() { return n; } }; struct Printer { virtual int get. Value() { return 0; } void print() { cout << get. Value() << endl; } }; struct Int : Printer, Number { }; int main(int, char**) { Int* n = new Int(); n->set. Value(5); n->print(); // Output: "0" ? ! } 2
Discussion #1 Printer is not a subclass of Number: : get. Value() does not override Printer: : get. Value() We want Number: : get. Value() to win But, when we look at the code we see that the two methods are symmetric 3
2 nd Int Class, 2 Attempt struct Number { int n; void set. Value(int v) { n = v; } virtual int get. Value() { return n; } }; struct Printer { virtual int get. Value() = 0; // Pure virtual function void print() { cout << get. Value() << endl; } }; struct Int : Printer, Number { }; int main(int, char**) { Int* n = new Int(); // “Cannot allocate an object of type Int” n->set. Value(5); n->print(); } 4
5 Discussion #2 We tried to break the symmetry Printer: : get. Value() is now abstract (Pure-virtual in C++ jargon) This does not solve the problem The only implementation of get. Value() is in Number Which is not a sub-class of Printer => There's no definition that overrides Printer: : get. Value() => in class Int, Printer: : get. Value() is unimplemented => class Int is abstract Cannot be instantiated
3 rd 3 Attempt: Mazal Tov struct Number { int n; void set. Value(int v) { n = v; } virtual int get. Value() { return n; } }; struct Printer { virtual int get. Value() = 0; void print() { cout << get. Value() << endl; } }; struct Int : Printer, Number { int get. Value() { return Number: : get. Value(); } }; int main(int, char**) { Int* n = new Int(); n->set. Value(5); n->print(); } 6
7 Discussion #3 Pros: 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 a winner! 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
Mixins A subclassing mechanism In normal inheritance we specify two things: S: Superclass D: Definition to copy == Delta to add to S. Mixins: two basic operations Based on the copying principle 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 8
Mixin: C++ struct Number { int n; void set. Value(int v) { n = v; } virtual int get. Value() { return n; } }; template<typename S> // Define the mixin struct Printer : S { void print() { cout << get. Value() << endl; } }; struct Int : Printer<Number> // Instantiate the mixin { }; int main(int, char**) { Int* n = new Int(); n->set. Value(5); n->print(); } 9
Properties of Mixins Separate the D from the S The resulting class inherits from D which inherits from S No extra work for gluing the two classes Expectations of D 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 10 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
C++: 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! } 11
Workaround for the Mixin-is-not-a-type problem struct Number { int get. Value() { 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 << get. Value() << endl; } }; struct Int : Printer<Number> { }; int main(int, char**) { Printable* p = new Int(); p->print(); } }; 12
Jam: Java with Mixins public class Number { private int n; void set. Value(int v) { n = v; } int get. Value() { return n; } }; mixin Printer { inherited public int get. Value(); public void print() { System. out. println(get. Value()); } } class Int = Printer extends Number { } public void f() { Int n = new Int(); n. set. Value(5); Printer p = n; } 13
Discussion: Jam Mixins All properties of C++ mixins Expectations are declared explicitly Separate the D from the S Linearization => Clear winner Using the inherited keyword Drawbacks are solved: The (uninstantiated) mixin class is a type D is compiled when defined (becomes an interface) 14
Linearization 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 { } 15
Linearization 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 16
Traits: Flattening 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 17
Java with Traits trait T 1 { abstract void add(int v); void inc() { add(1); } } trait T 2 { abstract int get. Value(); abstract void set. Value(int v); void add(int v) { set. Value(get. Value() + v); } } class Int uses T 1, T 2 { int n; int get. Value() { return n; } void set. Value(int v) { n = v; } } T 1 t 1 = new Int(); t 1. add(3); // A trait is also type 18
Conflict Resolution trait T 1 { void add(int v) { while(--v >= 0) inc(); } void inc() { add(1); } } trait T 2 { abstract int get. Value(); abstract void set. Value(int v); void add(int v) { set. Value(get. Value() + v); } } class Int uses T 1, T 2 { // Int can also extend a “normal” superclass int n; int get. Value() { return n; } void set. Value(int v) { n = v; } void add() { T 2. this. add(); } // Resolve the conflict // Otherwise, a compiler error // when compiling Int } 19
20 Class Composition vs. Object Composition So far we saw mechanisms for creating new classes from existing ones Q: Can we extend existing objects? E. g. , add new methods, fields to an object? A: In Dynamically typed languages - Yes AKA: Class composition Especially in Javascript, Ruby In LST – partially What about statically typed languages?
Object Composition - Motivation package 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 get. Red() {. . . } public class My. Color extends Color { public boolean is. Redder(Color c) { return get. Red() > c. get. Red(); } } // We want to evaluate: Color. RED. is. Redder(Color. GREEN) // But, we can't because we cannot change the initialization of Color. RED 21
22 Object Composition – Manual Delegation public class My. Color extends Color { private Color inner; public My. Color(Color inner) { this. inner = inner; } public int get. Red() { return inner. get. Red(); } public int get. Green() { return inner. get. Green(); } public String to. String() { return "My. Color: " + inner. to. String(); }. . . public boolean is. Redder(Color c) { return get. Red() > c. get. Red(); } } My. Color mc = new My. Color(Color. RED); mc. is. Redder(Color. GREEN)
23 Drawbacks of Manual Delegation A lot of typing Silent errors: If a new method is added In the outer and the inner object But we only use the inner one The “overriding” semantics is only from the outside We must remember to add a forwarding implementation at the outer class We have two sets of fields Must redefine every method of the inner object 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)
interface Value int get(); void print(); } Overriding is Partial { class Value. Impl 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 Value. Impl()): v. get(); // Result: 10 : ) v. print(); // Output: "5" : ( 24
Lava: 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 } } 25
Delegation 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(); 26
3db2290a2a28ab25990827d156073ac4.ppt