340 likes | 987 Views
Inheritance & Delegation. Our Starting Point. We know OOP Objects are instances of classes Overriding We have used inheritance. Terminology. Per class Forge: Signatures of accessible constructors Mill: Bodies of constructor Protocol: Signatures of accessible fields, methods
E N D
Our Starting Point • We know OOP • Objects are instances of classes • Overriding • We have used inheritance
Terminology • Per class • Forge: Signatures of accessible constructors • Mill: Bodies of constructor • Protocol: Signatures of accessible fields, methods • Behavior: Bodies of methods • Structure: Memory layout of an object • Per object • State: Values held by fields of an object • Identity: Distinguishes two objects
The Two Effects of Subclassing • Code Reuse • code of superclass is available in subclass • Polymorphism • Instances of subclass can masquerade as instances of superclass • Next slide: • [Reuse] No upcasting of KShortestPathsIterator • [Reuse] No upcasting of ArrayList • [Polymorhism] PathWrapper is added to ArrayList<GraphPath>
public List<GraphPath<V, E>> getPaths(V endVertex) { KShortestPathsIterator<V, E> iter = new KShortestPathsIterator<V, E>(graph, startVertex, endVertex, nPaths); for(int passNumber = 1; (passNumber <= nMaxHops) && iter.hasNext(); passNumber++) iter.next(); List<RankingPathElement<V, E>> list = iter.getPathElements(endVertex); if (list == null) return null; ArrayList<GraphPath<V, E>> pathList = new ArrayList<GraphPath<V, E>>(); for (RankingPathElement<V, E> element : list) pathList.add(new PathWrapper(element)); return pathList; } // Taken from: org.jgrapht.alg.KShortestPaths
Polymorphic Messages • Same message will trigger different reactions • Very powerful mechanism • Previously (no polymorphic messages) for(Product x : products) { double price = x.fixedPrice ? x.price : x.price*0.7; System.out.println("New price is " + price); } • W/ polymorphic messages for(Product x : products) { double price = x.discountPrice(0.7); .. } • Implementing classes: Product, FixedPriceProduct
class Product { double price; public Product(double p) { price = p; } public double discountPrice(double factor) { return factor * price; } } class FixedPriceProduct extends Product { public FixedPriceProduct(double p) { super(p); } public double discountPrice(double factor) { return price; } }
Subclass can Break Superclass public class Range { protected int b, e; void setBegin(int n) { b = n; } void setEnd(int n) { e = n; } void shift(int n) { setBegin(b + n); setEnd(e + n); } } public class PositiveRange { private void check() { if(e < b) throw new RuntimeException(); } void setBegin(int n) { super.setBegin(n); check(); } void setEnd(int n) { super.setEnd(n); check(); } }
Semantic Mismatch class Product { double price, cost; Product(double p, double c) { price = p; cost = c; } void getProfit() { return price – cost; } double getPrice() { return p; } void setPrice(double p) { price = p; } } class FixedPriceProduct extends Product { FixedPriceProduct(double p, double c) { super(p, c); } void setPrice(double p) { } // Price never changes! }
Semantic Mismatch (cont.) class Store { Product[] products; void increaeProfit(double factor) { for(Product p : products) { double diff = factor * p.getPrice(); p.setPrice(p.getPrice() + diff); } } // Problem: increaseProfit(0.1) will not always lead // to 10% increase in profit !!
Semantic Mismatch: Summary • The author of increaseProfit expected setPrice to change the price of the product • This is true for class Product • This is not true for FixedPriceProduct • Sometimes one can find a neutral implementation • Provides the semantics the caller expects • Does not break the invariants of the class • E.g., the discountPrice() method • (Sadly, this is not the case here) • The standard solution: adjust the protocols • FixedPriceProduct will not offer a setPrice() method
Adjusting the Protocols class Product { double price, cost; Product(double p, double c) { price = p; cost = c; } void getProfit() { return price – cost; } double getPrice() { return p; } } class FixedPriceProduct extends Product { FixedPriceProduct(double p, double c) { super(p, c); } } class NormalProduct extends Product { NormalProduct(double p, double c) { super(p, c); } void setPrice(double p) { price = p; } }
Adjusting the Protocols (cont.) class Store { Product[] products = ...; NormalProduct[] normalProducts = ...; void increaeProfit(double factor) { for(NormalProduct p : normalProducts) { double diff = factor * p.getPrice(); p.setPrice(p.getPrice() + diff); } } // Problem: Distinction between NormalProduct and // FixedPriceProduct has leaked into class Store. // We lost the benefits of polymorphic messages
Altering Behavior • Behavior is defined by bodies of methods • Overriding allows a programmer to change a method’s body • => Altered behavior can be obtained by means of overriding
// First example, a Car class public class Car { private int speed; public void setSpeed(int s) { speed = s; } public int getSpeed() { return speed; } } public class TalkingCar extends Car { public void setSpeed(int s) { super.setSpeed(s); System.out.println("My new speed is " + s); } }
// Second example, a scrabble game public class Letter { private char ch; public Letter(char c) { ch = c; } public boolean matches(char c) { return ch == c; } } public class WildCard extends Letter { public boolean matches(char c) { return true; } }
// Third example, scrabble game again public interface Letter { boolean matches(char c); } public class StandardLetter implements Letter { private char ch; public Letter(char c) { ch = c; } public boolean matches(char c) { return ch == c; } } public class WildCard implements Letter { public boolean matches(char c) { return true; } }
Is overriding the only way to alter behavior? • No! • One can use state to alter behavior
// Varying a car behavior via state public class Car { private int speed; private String message; private Car(String m) { message = m; } public Car() { this(""); } public void setSpeed(int s) { speed = s; System.out.format(message, s); } public int getSpeed() { return speed; } public static Car newTalkingCar() { return new Car("My new speed is %s\n"); } }
// Scrabble: Emulating the wild-card via state public class Letter { private char ch; public Letter(char c) { ch = c; } public boolean matches(char c) { return ch == '\0' || ch == c; } public static newWildCard() { return new Letter('\0'); } }
Varying State vs. Overriding • Is state as powerful as overriding? • Yes! • The general solution: Delegation
Delegation • The receiver object forwards the incoming message to another object • (Respectively: delegating, delegated) • Delegated performs the “actual” work • Delegating holds the delegated in a field • New behavior: replace the delegated-to object • Overriding effect via varying state • Delegated object can offer only a single method • Function Object/Functor/Block/Closure/Command • A delegating object can hold several delegated-to objects • A delegated object can in turn delegate to another object • Following two slide show the general recipe for replacing overriding w/ delegation
public class C1 { public void f() { // do something } } public class C2 extends C1 { public void f() { // do something else } }
public interface I { public void f(); } public class X1 implements I { public void f() { // do something } } public class X2 implements I { public void f() { // do something else } } public class C { private I i; public void f() { i.f(); } private C(I i) { this.i = i; } public static newC1() { return new C(new X1()); } public static newC2() { return new C(new X2()); } }
Delegation in Practice • Delegating object does some extra work • Wraps the call (synchronize, catch, …) • Caching • Calls a method with a different name • Passes some extra parameters • Computes some other result • … • Here’s a realistic delegation scenario
// No delegation public class ReportGenerator { private String[] names; protected int compareNames(String s1, String s2) { return s1.trim().compareToIgnoreCase(s2.trim()); } protected void sort() { ... compareNames(...) ... } public void printReport(PrintWriter out) { sort(); for(String name : names) printName(out, name); } protected void printName(PrintWriter out, String name) { out.println(name); } }
// With delegation public class ReportGenerator { private String[] names; private Comparator<String> comparator; private NameFormatter formatter; public void setFormatter(NameFormatter nf) { ... } public void setComparator(Comparator<String> c) { ... } protected void sort() { ... comparator.compare(...) ... } public void printReport(PrintWriter out) { sort(); for(String name : names) printName(out, name); } protected void printName(PrintWriter out, String name) { out.println(formatter.format(name)); } }
Delegation: Pros • Modify behavior of an existing object • Change the formatter of a ReportGenerator • (After it was created!) • Eliminate combinatorial explosion • 4 name formats • 4 name comparison policies • Total of 16 combinations • Overriding: 16 subclasses • Delegation: 4 formatters, 4 comparators • Assists in increasing coherency • Dismantling a class into unrelated parts
Creation Point Issues • Subclassing requires access to creation point • (The class is determined at creation time) • Creation point may not be accessible • Objects created by a library • Logic of choosing between subclasses may temper coherency
Delegation: Cons • Harder to understand the code • Even the dynamic type does not specify the behavior • => Harder to debug • In simple cases requires more code • Difficult to prevent illegal combinations • E.g.: A formatter that requires a specific comparator • Multiple identities