280 likes | 445 Views
Classes (part II). Roadmap. Emulating overriding w/ state Delegation vs. Subclassing Binary Methods LSP Immutability Interfaces vs. Classes Law of Demeter: “Tell, don’t ask” Message chains Defensive copies Bad API Exception safety Origins of classes – nouns Problem vs. Program space
E N D
Roadmap • Emulating overriding w/ state • Delegation vs. Subclassing • Binary Methods • LSP • Immutability • Interfaces vs. Classes • Law of Demeter: “Tell, don’t ask” • Message chains • Defensive copies • Bad API • Exception safety • Origins of classes – nouns • Problem vs. Program space • Origins of classes – Kent Beck • Fat vs. thin interfaces • Furniture - subclassing alternatives
Origins of Classes • Grouping of data and logic • Rate of change • Parameter objects • Overall approach: start writing, discover the classes from the code • Source: “Implementation Patterns”, Kent Beck
Grouping of data and logic Use a class to say, “This data goes together and this logic goes with it” “Implementation Patterns”, Kent Beck
Grouping – Before if (ClassReader.SIGNATURES && signature != 0) { ++attributeCount; size += 8; newUTF8("Signature"); } if (sourceFile != 0) { ++attributeCount; size += 8; newUTF8("SourceFile"); } ...
Grouping – After AttributeWriter aw = new AttributeWriter(...); aw.put(ClassReader.SIGNATURES && signature != 0, 8, "Signature"); aw.put(sourceFile != 0, 8, "SourceFile"); ...
Rate of Change Put logic or data that changes at the same rate together and separate logic or data that changes at different rates “Implementation Patterns”, Kent Beck
Rate of Change - Example // Before void setAmount(int value, String currency) { this.value = value; this.currency = currency; } // After void setAmount(int value, String currency) { this.value = new Money(value, currency); }
Parameter Object Consolidate frequently used long parameter lists into an object “Implementation Patterns”, Kent Beck
Parameter Object - Example // Before setOuterBounds(x, y, width, height); setInneerBounds(x + 2, y + 2, width - 4, height - 4); // After setOuterBounds(bounds); setInnerBounds(bounds.expand(-2));
Interfaces: Width • Thin • Support minimal set of necessary services • Humane • Support common usage • Fat • Support every imaginable service • Textbook example: • Java’s List inteface has 25 methods • Ruby’s Array class has 78 methods Source: http://martinfowler.com/bliki/HumaneInterface.html
Humane Interface public class HumaneList<T> { public T get(); public void remove(int n); public int size(); public void set(int index, T value); public void add(T value); public T first(); public T last(); public void sort(); public int indexOf(T value); }
Thin Interface public class ThinList<T> { public T get(); public void remove(int n); public int size(); public void set(int index, T value); } // Client code – add a value list.set(list.size(), newValue); // Client code – get last List.get(list.size() – 1);
Thin Interfaces Encourage Foreign Methods public class Lists { public static<T> void add(List<T> list, T value) { list.set(list.size(), value); } public static<T> T getLast(List<T> list, T value) { list.get(list.size() – 1); } public static<T> void sort(...) public static<T> int IndexOf (...) ... }
Thin vs. Humane • Thin interfaces – pros: • Coherency • of implementing class • of the interface itself • Easier subclassing • Client code is less coupled • Humane Interface – cons: • Less client code
Thin/Humane – Final Thoughts • A spectrum, not a binary issue • An interface can be slightly humane and very much thin • Inside project boundaries? • Start with thin, let it evolve • Unknown users? • Humane seems more useful • OTOH, more difficult to fight coupling
public class Rect { public final int top; public final int left; public final int bottom; public final int right; public Rect(int top, int left, int bottom, int right) { this.top = top; this.left = left; this.bottom = bottom; this.right = right; } } public class Point { public final int x; public final int y; public Point(int x, int y) { this.x = x; this.y = y; } }
public abstract class Furniture { public abstract Rect getBoundingRect(); } public class Table extends Furniture { private int width; private int height; public Table(int width, int height) { this.width = width; this.height = height; } @Override public Rect getBoundingRect() { return new Rect(0, 0, width, height); } }
public class Lamp extends Furniture { private int base; private int height; public Lamp(int base, int height) { this.base = base; this.height = height; } @Override public Rect getBoundingRect() { return new Rect(0, 0, base, height); } }
public class Furniture { final List<Point> points = new ArrayList<Point>(); protected void add(int x ,int y) { points.add(new Point(x, y)); } public Rect getBoundingRect(){ List<Integer> xs = new ArrayList<Integer>(); List<Integer> ys = new ArrayList<Integer>(); for(Point p : points) { xs.add(p.x); ys.add(p.y); } return new Rect(min(ys), min(xs), max(ys), max(xs)); } }
public class Furniture { ... static int max(List<Integer> values) { int result = Integer.MIN_VALUE; for(int n : values) result = Math.max(result, n); return result; } static int min(List<Integer> values) { int result = Integer.MAX_VALUE; for(int n : values) result = Math.min(result, n); return result; } }
public class Table extends Furniture { public Table(int width, int height) { add(0, 0); add(width, 0); add(width, height); add(0, height); } } public class Lamp extends Furniture { public Lamp(int base, int height) { add(0, 0); add(base, 0); add(base / 2, height); } }
Public class Furniture { ... // Same code as design #2 public Furniture newTable(int width, int height) { Furniture result = new Furniture(); result.add(0, 0); result.add(width, 0); result.add(width, height); result.add(0, height); return result; } public Furniture newLamp(int base, int height) { Furniture result = new Furniture(); result.add(0, 0); result.add(base, 0); result.add(base / 2, height); return result; } }
Summary • Design #1: • getBoundingRect() over-ridden • Subclasses do most of the work • Elegant if computations vary greatly across subclasses • Design #2, #3: • Superclass does most/all of the work • Variation among subclasses expressed by state • Elegant if computations can be generalized • Without too many special cases crippling it • More efficient as number of variants grows