Principles of Object Oriented Class Design ========================================== 1) Which factors compromise the maintainability of software? - Rigidity: changes in one place cascade to other places. 1.1) Examples? - Immobility: software that is hard to reuse. 1.2) Examples? - Hacks: simple changes that are hard to maintain. 1.3) Examples? These are principles that help to manage the dependencies between modules. * Open-closed principle: we should be able to extend our modules without having to modify them. 2) What are the problems with the following payment system? class Employee { private int hours; public Employee(int hours) { this.hours = hours; } public int getHours() { return hours; } } class Manager extends Employee { private int numSubordinates; public Manager(int hours, int numSubs) { super(hours); numSubordinates = numSubs; } public int getNumSubs() { return numSubordinates; } } class Guard extends Employee { private double dangerFactor; public Guard(int hours, double df) { super(hours); dangerFactor = df; } public double getDangerFactor() { return dangerFactor; } } public class Payment { public double getPay(Employee e) { if (e instanceof Manager) { return 1000.0 * ((Manager)e).getNumSubs() + 200.0 * e.getHours(); } else if (e instanceof Guard) { return 800.0 * ((Guard)e).getDangerFactor() + 160.0 * e.getHours(); } else { return 600.0 * e.getHours(); } } public static void main(String args[]) { } } 3) How the system would have to be modified to add a new type of Employee? Say, SuperEmployee, who makes more money? 4) Why this program fails to be object-oriented? 5) And how to fix it? 6) Now, what if we want to ensure that SuperEmployees get payed first? - ex3 7) Is it possible to close the system against ordering? - ex4 public class Employee implements Comparable { static Map ordering = new HashMap(); static { ordering.put(Employee.class, 0); } ... public int compareTo(Employee o) { int ord = ordering.get(this.getClass()) - ordering.get(o.getClass()); if (ord == 0) { return this.hashCode() - o.hashCode(); } else { return ord; } } } public class Manager extends Employee { static { ordering.put(Manager.class, 5); } } 8) Encapsulation is one of the key heuristics behind the open-closed principle. Why? 9) Consider a variable that we know that is not likely to change. Say: public class Device { public boolean status; } Is there any problem to set it public? 10) What about: public class Time { int hours; int minutes; int seconds; } 10) How do global variables compromise the open-closed principle? 11) Run-time type interrogation (RTTI) is another source of violations of the OCP principle. Examples? 12) But there are also examples of situations when the use of RTTI is safe, i.e, it does not violate the OCP principle. Which examples? - E.g: want to know how many guards we have in the payroll. 13) A good heuristic to support the OCP principle is "programming to the interfaces". How so? 14) The OCP principle could be re-phrased as "New features are added by adding new code, instead of changing old code". * The Liskov substitution principle: subclasses should be substitutable for their base classes. 15) Any situation that would break this principle? 16) How to reuse the Rectangle class below to implement a Square? public class Rectangle implements Shape { private int x, y, w, h; public Rectangle(final int xp, final int yp, final int wp, final int hp){ this.x = xp; this.y = yp; this.w = wp; this.h = hp; } public void setWidth(int newW) { this.w = newW; } public void setHeigth(int newH) { this.h = newH; } public int area() { return w * h; } public final void draw(final Graphics c) { c.drawRect(x, y, w, h); } } 17) How to handle this test? public class TestRect { static void testArea(Rectangle r, int h, int w) throws Exception { r.setHeight(h); r.setWidth(w); if (r.area() != h * w) { throw new Exception("Inconsistent geometry!"); } } public static void main(String args[]) throws Exception { testArea(new Rectangle(50, 50, 10, 20), 20, 40); testArea(new Square(50, 50, 20), 20, 40); } } 18) What is the post-condition of, say, setWidth in Rectangle? public void setWidth(int newW) { int oldH = h; w = newW; assert(w == newW && oldH == h); } 18.1) Is this true for Square? 19) How does inheritance impact on pre and post conditions? ... when redefining a routine in a subclass, only replace a pre-condition by a weaker pre-condition, and a post-condition by a stronger one. 19.1) Example? 20) How does our implementation of square change this? 21) Other examples? 22) Ok, perhaps Squares are not a "Real life example"... Any problem in the code below? import java.util.*; public class TestRect2 { public static void getMockData(Collection c) { c.add(new Rectangle(5, 50, 10, 20)); c.add(new Rectangle(50, 5, 10, 20)); c.add(new Square(50, 5, 10)); c.add(new Square(51, 5, 10)); } public static void main(String args[]) throws Exception { Collection c = new TreeSet(); getMockData(c); for (Rectangle r : c) { System.out.println(r); } } } 23) Many times the best approach is to avoid a method from being changed altogether. How to avoid a method from being overwritten in Java? 23.1) How would that approach apply to Rectangle? 24) Take the implementation of MyList and produce a PersistentMyList, so that it stores your data into a file: import java.io.*; public class PersistentCollection extends MyList { private ObjectOutputStream oos; private String fileName; public PersistentCollection(String name) { try { fileName = name; oos = new ObjectOutputStream(new FileOutputStream(new File(fileName))); } catch (Exception e) { e.printStackTrace(); } } public void add(E e) { try { oos.writeObject(e); } catch (Exception e1) { e1.printStackTrace(); } super.add(e); } public void set(int i, E e) { try { oos.close(); oos = new ObjectOutputStream(new FileOutputStream(new File(fileName))); super.set(i, e); for (E aux : this) { oos.writeObject(aux); } } catch (Exception e1) { e1.printStackTrace(); } } public E get(int i) { return super.get(i); } } 25) How to guarantee that the file is close, upon the object destruction? protected void finalize() throws Throwable { try { oos.close(); } finally { super.finalize(); } } 26) What would happen if we tried to insert instances of the Time class below into our persistent list? public class Time { public int hours, minutes, seconds; public Time(int h, int m, int s) { hours = h; minutes = m; seconds = s; } public String toString() { return hours + ":" + minutes + ":" + seconds; } } 27) How to fix that? import java.io.Serializable; public class Time implements Serializable { public int hours, minutes, seconds; public Time(int h, int m, int s) { hours = h; minutes = m; seconds = s; } public String toString() { return hours + ":" + minutes + ":" + seconds; } } 28) How to change the contract of PersistentCollection so that it only receives Serializable elements? import java.io.Serializable; public class PersistentCollection extends MyList { ... } 29) How does rigidity, immobility and hacks come in in the examples that we have seen today?