1 / 47

Class-level Design

Class-level Design. Reminder: Software is Recursive. Civil engineering: Room, Flat, building, Street, City, … Software Class, Class, Class, ... Or: Object, Object, Object, … => A class can represent both low level concerns and high level concerns => Design of classes ~ design of a program.

brina
Download Presentation

Class-level Design

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Class-level Design

  2. Reminder: Software is Recursive • Civil engineering: • Room, Flat, building, Street, City, … • Software • Class, Class, Class, ... • Or: • Object, Object, Object, … • => A class can represent both low level concerns and high level concerns • => Design of classes ~ design of a program

  3. Interfaces: Visibility • PublicThe group of methods (of a class) that any other class in the system can invoke • PublishedA class interface that is used outside the code base that it is defined in Source: Martin Fowler; Public versus Published interfaces; IEEE Software 2002

  4. 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

  5. Humane Interface public class HumaneList<T> { public T get(int i); 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); }

  6. Thin Interface public class ThinList<T> { public T get(int i); 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);

  7. 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) { list.get(list.size() – 1); } public static<T> void sort(...) public static<T> int IndexOf (...) ... }

  8. 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

  9. 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

  10. Mission Statement A program which manipulates the location of furniture in a room. A furniture can be a chair a table or a lamp. Bounding rectangles of furniture should not intersect and should be contained inside the room. Doors should be located along the boundaries of the room.

  11. Problem Space Classes • Room • Chair, Table, Lamp, Door • Rectangle • …

  12. Program Space Classes • Range • Placement

  13. Problem Space • Characteristics are dictated externally • Some may change in unanticipated ways • See the axioms • Limited reuse opportunities

  14. Program Space • Characteristics chosen by programmer • High degree of reuse: The Lego principle • => Difficult to change

  15. Furniture – Design #1

  16. 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; } }

  17. 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); } }

  18. 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); } }

  19. Furniture – Design #2

  20. 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)); } }

  21. 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; } }

  22. 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); } }

  23. Furniture – Design #3

  24. 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; } }

  25. 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

  26. Immutability public class Driver { ... }// Mutable:public class Car { private Driver d; public Driver getDriver() { return d; } public void setDriver(Driver d_) { d = d_; }}// Immutable:public class Car { private final Driver d; public Car(Driver d_) { d = d_; } public Driver getDriver() { return d; }}

  27. Immutability (cont.)‏ • Pros: • Compiler-checked contract • Covariant sub-classing • Thread safety • Caching, Sampling • Only the constructor throws (usually) • Less exception handling on the client's side • Better exception safety • Performance: Less copying • Cons: • Mutations

  28. An Interface or a Classes? public void g(List<Nameable> list) { for(Nameable n : list) System.out.println(n.getName());}// Opt.1: Person is an interfacepublic interface Nameable { public String getName();}// Opt.2: Person is a classpublic abstract class Nameable { public abstract String getName(); }

  29. Interfaces Vs. Classes • Interfaces • Do not fill the inheritance spot • Easier to provide alternative implementations • Promise less – client is less coupled • => Client is more reusable • => Client is more complicated • Classes • Easier to read the code • Promise more

  30. Interfaces vs. Classes (one more time)‏ public interface MyList { public int getHead(); public List getTail();}public class MyList implements List { private final int head; private final MyList tail; public MyList(int head_, MyList tail_) { head = head_; tail = tail_; } public final int getHead() { return head; } public final MyList getTail() { return tail; } }public static boolean exist(int n, MyList lst) { return lst == null ? false : (lst.getHead() == n ? true : exist(n, lst.getTail())); }

  31. <bank.classes>

  32. public class Bank { private Map<Integer,Account> accounts = new HashMap<Integer,Account>(); public Account getAccount(int id){ return accounts.get(id); } } public class Account { private final Owner owner; private int amount; public Account(Owner o) { owner = o; } public Owner getOwner() { return owner; } public void deposit(int n) { amount += n; } public int getAmount() { return amount; } }

  33. public class Owner { private final String name; private final Branch branch; public Owner(String name_, Branch branch_) { branch = branch_; name = name_; } public String getName() { return name; } public Branch getBranch() { return branch; } }

  34. public class Branch { String address; Map<Date,String> meetings = new HashMap<Date,String>(); public Branch(String address_) { address = address_; } public String getAddress() { return address; } public Date requestMeeting(String subject) { Date d = ... // Choose a date for the meeting meetings.put(d, subject); return d; } } public class Reporter { public void printAddress(Bank b, int id) { System.out.println(id + ": " + b.getAccount(id).getOwner().getBranch().getAddress()); } }

  35. Tell vs. Ask • AskAccount a = ...; a.getOwner().getBranch().requestMeeting("…"); • TellAccount a = ...; a.requestMeeting("…"); • Guideline: Tell, Don’t ask • Better encapsulation • Let Account control the behaviour of requestMeeting • Related Smell: Long Message Chains • “Watch out for long sequences of method calls...”

  36. Law of Demeter • A method M of an object O may only invoke the methods of the following kinds of objects • 1. O itself • 2. M's parameters • 3. any objects created within M • 4. O's direct fields • Embodies the "tell, don’t ask" guideline

  37. Class Account (Demeterized)‏ public class Account { private final Owner owner; private int amount; public Account(Owner o) { owner = o; } public void deposit(int delta) { amount += delta; } public int getAmount() { return amount; } public String getOwnerName() { return owner.getName(); } public String getBranchAddress() { return owner.getBranchAddress(); } public Date requestMeeting(String subject) { return owner.requestMeeting(subject) : null; } }

  38. Class Owner (Demeterized)‏ public class Owner { private final String name; private final Branch branch; public Owner(String name_, Branch branch_) { branch = branch_; name = name_; } public String getName() { return name; } public Branch getBranch() { return branch; } public String getBranchAddress() { return branch.getAddress(); } public Date requestMeeting(String subject) { return branch.requestMeeting(subject); } }

  39. Benefits of the "Tell" Approach // Class Account can control the requestMeeting behavior public class Account { private final Owner owner; private int amount; private int limit = 5; public Account(Owner o) { owner = o; } public void deposit(int delta) { amount += delta; } public int getAmount() { return amount; } public String getOwnerName() { return owner.getName(); } public String getBranchAddress() { return owner.getBranchAddress(); } public Date requestMeeting(String subject) { return limit-- > 0 ? owner.requestMeeting(subject) : null; } }

  40. Law Of Demeter • Pros • Better Encapsulation • Easy to intercept/modify requests • Cons • Low Cohesion • High Coupling • Smells: • Combinatorial explosion • Divergent change (lack of cohesion)‏ • Shotgun surgery • Middle man

  41. Defensive Setters/Getters • (Relevant under the “Ask” approach) • Instead of returning an object… • …Return a copy thereof • Expected to increase encapsulation

  42. Class Bank: Duplicated Data public class Bank { private Map<Integer,Account> accountFromId = new HashMap<Integer,Account>(); private Map<String,Integer> idFromName = new HashMap<String,Integer> public Account getAccount(int id){ return accountFromId.get(id); }public void addAddcount(int id, Account a) { accountFromId.put(id, a); idFromName.put(a.getOwner().getName(), id); }public Account getAccount(String name) { return getAccount(idFromName.get(name)); } }

  43. Huston, We Have a Consistency Problem //// Let's assume Owner has a setName() method...// public void f1(Bank b) { b.getAccount(1).getOwner().setName("new-name"); } public void f2(Owner o) { o.setName("new-name"); }

  44. Defensive Copies public class Account { private final Owner owner; private int amount; public Account(Owner o) { owner = o; } public Owner getOwner() { return owner.clone(); } public void deposit(int delta) { amount += delta; } public int getAmount() { return amount; } }

  45. Class Owner (Defensive Copies)‏ public class Owner implements Cloneable { private String name; private final Branch branch; public Owner(String name_, Branch branch_) { branch = branch_; name = name_; } @Override protected Owner clone() { try { return (Owner) super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } } public void setName(String arg) { name = arg; } public String getName() { return name; } public Branch getBranch() { return branch; } }

  46. Defensive Copies: The Pitfall public void g(Account a) { a.getOwner().setName("new-name"); a.getOwner().getBranch().setAddress("new-address"); }

  47. Defensive Copies: Summary • Never underestimate the importance of DRY • No duplicated data => no need to be defensive • E.g.: caching, data computed from raw data • Useful when returning objects that were not passed in from the outside • E.g.: Collections • Textbook example: Class.getMethods() • Can be used in either setters or getters

More Related