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 Interfacepublic 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 Interfacepublic 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
Immutabilitypublic 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• Ask
Account 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 behaviorpublic 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 Datapublic 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)); }}
43Huston, 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