// open recursion class Parity { boolean even(int i) { if(i==0) return true; return odd(i-1); } boolean odd(int i) { if(i==0) return false; return even(i-1); } } class FastParity { boolean odd(int i) { return (i % 2) == 1; } // improves even } class BadParity { boolean odd(int i) { return odd(i); } // breakes even } // simple expressions abstract class Exp { abstract public int eval(); abstract public String makeString(); // yes, could override toString abstract public boolean hasZero(); } class Int extends Exp { int i; Int(int _i) { i = _i; } public int eval() { return i; } public String makeString() { return Integer.toString(i); } public boolean hasZero() { return i==0; } } class Negate extends Exp { Exp e; Negate(Exp _e) { e = _e; } public int eval() { return - e.eval(); } public String makeString() { return "-(" + e.makeString() + ")"; } public boolean hasZero() { return e.hasZero(); } } class Add extends Exp { Exp e1; Exp e2; Add(Exp _e1, Exp _e2) { e1 = _e1; e2 = _e2; } public int eval() { return e1.eval() + e2.eval(); } public String makeString() { return "(" + e1.makeString() + " + " + e2.makeString() + ")"; } public boolean hasZero() { return e1.hasZero() || e2.hasZero(); } } class Mult extends Exp { Exp e1; Exp e2; Mult(Exp _e1, Exp _e2) { e1 = _e1; e2 = _e2; } public int eval() { return e1.eval() * e2.eval(); } public String makeString() { return "(" + e1.makeString() + " * " + e2.makeString() + ")"; } public boolean hasZero() { return e1.hasZero() || e2.hasZero(); } } // now we want to add a variant -- easy class Power extends Exp { Exp e1; Exp e2; private int pow(int x, int y) { // only correct for y >= 0 return y==0 ? 1 : x*pow(x,y-1); } Power(Exp _e1, Exp _e2) { e1 = _e1; e2 = _e2; } public int eval() { return pow(e1.eval(), e2.eval()); } public String makeString() { return "(" + e1.makeString() + " ^ " + e2.makeString() + ")"; } public boolean hasZero() { return e1.hasZero() || e2.hasZero(); } } // but adding an operation is not so easy... interface ExpBad { int eval(); String makeString(); boolean hasZero(); int sumInts(); } class NegateBad extends Negate implements ExpBad { // must have constructor take an Exp, but want to require ExpBad! NegateBad(Exp e) { super(e); } // downcast is a potential run-time failure! public int sumInts() { return ((ExpBad) this.e).sumInts(); } } // ... // If we can modify existing code, we can add a new abstract method to Exp // and a new concrete method in each subclass. // * Exhaustiveness checking on abstract methods means the type-checker lists // what needs doing. // * A great reason not to implement methods that throw "cannot handle" // exceptions. // we can make a "parallel hierarchy" abstract class ExpSum { abstract Exp toExp(); // could do better with interfaces abstract int sumInts(); } class IntSum extends ExpSum { Int i; IntSum(int _i) { i = new Int(_i); } Exp toExp() { return i; } int sumInts() { return i.i; } } class NegateSum extends ExpSum { ExtExp e; NegateSum(ExtExp _e) { e = _e; } Exp toExp() { return new Negate(e.toExp()); } int sumInts() { return e.sumInts(); } } // ... // There is a clever use of dynamic dispatch to allow operation // extensibility. It is called the "visitor pattern" or "double // dispatch". I highly recommend learning it.