Contents [0/35] |
Subclassing: Objective/Subjective [1/35] |
Objective is applied to things exterior to the mind, and objects of its attention; subjective, to the operations of the mind itself. Hence, an objective motive is some outward thing awakening desire; a subjective motive is some internal feeling or propensity. Objective views are those governed by outward things; subjective views are produced or modified by internal feeling. Sir Walter Scott's poetry is chiefly objective; that of Wordsworth is eminently subjective. --- Webster's Revised Unabridged Dictionary (1913)
In the philosophy of mind, subjective denotes what is to be referred to the thinking subject, the ego; objective what belongs to the object of thought, the non-ego. --Sir. W. Hamilton
Normally we think of class interface as objective: What can we do to an instance?
The subjective interface is that visible to subclasses: What can an instance do to itself?
Subclassing: Designing for Subclassing [2/35] |
java.util.Observable is designed to be subclassed.
The public interface:
public class Observable { public Observable(); public void addObserver(Observer o); public void deleteObserver(Observer o); public void notifyObservers(); public void notifyObservers(Object arg); public void deleteObservers(); public boolean hasChanged(); public int countObservers(); ...
The protected interface:
... protected void setChanged(); protected void clearChanged();
So to specify that the state is changed you have to be "inside" the observable...
The class is useless if you don't subclass it.
public class Counter extends Observable { private int _i; CounterObj() { _i=0; } public void inc() { _i++; super.setChanged(); // could replace "super" with "this" or nothing } } public class Main { public static void main(String[] args) { Counter c = new Counter(); c.addObserver(new Observer() { public void update(Observable sender, Object data) { Counter c = (Counter) sender; String arg = (String) data; System.out.println("update(" + c + "," + arg + ")"); }}); c.inc(); c.inc(); c.notifyObservers("Dog"); } }
Counter.deleteObservers() is public!
At the object level:
+-----------+ | : Counter | | --------- | +-----------+
At the class level:
+------------+ +------------+ | Counter |- - - -|>| Observable | +------------+ +------------+
Subclassing: Designing for Delegation [3/35] |
file:stdlib/SubscriptionsTEST.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package stdlib; import static org.junit.Assert.*; import org.junit.Test; public class SubscriptionsTEST { public static class Counter { private Subscriptions<Counter,String> oh = new Subscriptions<Counter,String>(this); public void addSubscriber(Subscriber<Counter,String> subscriber) { oh.addSubscriber(subscriber); } public void notifySubscribers(String data) { oh.notifySubscribers(data); } int j; public int get() { return j; } public void inc() { j++; oh.setChanged(); } public String toString() { return "Counter(" + j + ")"; } } @Test public void testA () { Counter c = new Counter(); c.addSubscriber((sender, data) -> System.out.println ("update(" + sender + "," + data + ")")); c.inc(); c.inc(); c.notifySubscribers("Dog"); } private String output; @Test public void testB () { Counter c = new Counter(); c.addSubscriber((sender, data) -> output = ("update(" + sender + "," + data + ")")); c.inc(); c.inc(); c.notifySubscribers("Dog"); assertEquals("update(Counter(2),Dog)",output); } }
file:stdlib/Subscriber.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package stdlib; /** * A generic subscriber which a class can implement when it wants to be informed * of changes in published objects. * * @param <T> the type of the publisher object * @param <U> the type of the optional data argument * @see Subscriptions */ public interface Subscriber<T, U> { /** * This method is called whenever the published object is changed. * * @param publisher the object which was the source of the notification. * @param data an optional data parameter which encapsulates any * additional data about the event */ void update(T publisher, U data); }
file:stdlib/Subscriptions.java [source] [doc-public] [doc-private]
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package stdlib; import java.util.ArrayList; import java.util.List; /** * A helper class which can be used to manage an object's subscriptions. * * @param <T> the type of the publisher * @param <U> the type of the optional data argument * @see Subscriber */ public final class Subscriptions<T, U> { private final T publisher; private final List<Subscriber<T, U>> subscribers; private boolean changed = false; /** * Initializes a new instance of the {@link Subscriptions} class with * zero subscribers. * * @param publisher the object being publisher * @throws IllegalArgumentException if publisher is <code>null</code> */ public Subscriptions(T publisher) { if (publisher == null) throw new IllegalArgumentException("publisher cannot be null"); this.publisher = publisher; this.subscribers = new ArrayList<Subscriber<T, U>>(); } /** * Adds an subscriber to the set of subscribers for the publisher object, * provided that it is not the same as some subscriber already in the set. * * @param subscriber * @throws IllegalArgumentException if the parameter subscriber is <code>null</code> */ public void addSubscriber(Subscriber<T, U> subscriber) { if (subscriber == null) throw new IllegalArgumentException("subscriber cannot be null"); if (!subscribers.contains(subscriber)) { subscribers.add(subscriber); } } /** * Indicates that the publisher object has no longer changed, or that it has * already notified all of its subscribers of its most recent change, so that * {@link #hasChanged()} will now return <code>false</code>. This method * is called automatically by the {@link #notifySubscribers} methods. * */ public void clearChanged() { changed = false; } /** * Returns the number of subscribers of publisher object. * * @return the number of subscribers of publisher object */ public int countSubscribers() { return subscribers.size(); } /** * Deletes an subscriber from the set of subscribers. Passing <code>null</code> * to this method will have no effect. * * @param subscriber the {@link Subscriber} to be deleted */ public void deleteSubscriber(Subscriber<T, U> subscriber) { subscribers.remove(subscriber); } /** * Clears the subscriber list so that the publisher object no longer has any * subscribers. */ public void deleteSubscribers() { this.subscribers.clear(); } /** * Returns <code>true</code> if the publisher object has changed; * otherwise, <code>false</code>. * * @return <code>true</code> if the publisher object has changed; * otherwise, <code>false</code> */ public boolean hasChanged() { return changed; } /** * If this object has changed, as indicated by the {@link #hasChanged()}, * then notify all of its subscribers and then clear the changed state. This * method is equivalent to calling <code>notifySubscribers(null)</code>. */ public void notifySubscribers() { notifySubscribers(null); } /** * If this object has changed, as indicated by the {@link #hasChanged()}, * then notify all of its subscribers and then clear the changed state. * * @param data optional event specific data which will be passed to the subscribers */ public void notifySubscribers(U data) { if (hasChanged()) { for (Subscriber<T, U> subscriber : subscribers) { subscriber.update(publisher, data); } clearChanged(); } } /** * Flags the publisher object as having changed. */ public void setChanged() { changed = true; } }
Disadvantage of subclassing in Java: must export interface of superclass.
Advantage of subclassing in Java: syntactically light, easy to share fields.
Subclassing: Delgation versus Subclassing [4/35] |
Read the following diagram as ``every instance of Counter has a reference to an instance of Subscriptions.''
+------------+ 1 +---------------+ | Counter |<>-----> | Subscriptions | +------------+ +---------------+
Subclassing defines a single object incrementally.
In JAVA, says that subclass interface extends superclass interface.
Read the following diagram as ``every instance of Counter is also an instance of Observable.''
+------------+ +------------+ | Counter |- - - -|>| Observable | +------------+ +------------+
At run time, objects have references to the objects they know about. This is the object graph or heap.
At compile time, classes are related via subclassing and subtyping (as well as other dependencies). This is the class hierarchy.
Subclassing: Abstract classes [5/35] |
Often an intended superclass will be abstract.
An abstract class cannot be instantiated. It may have abstract methods.
java.util.Observable should be abstract, since it is useless unless subclassed. (Alternatively setChanged() and clearChanged() should be public.)
java.awt.Component is abstract.
Subclassing: Final classes [6/35] |
A final class cannot be subclassed.
An final class may not have abstract methods.
java.lang.String is final.
Subclassing: How to choose? [7/35] |
General rule (GoF): prefer delegation.
Main reason: choices can be delayed. Subclassing is fixed at compile time. Delegation can be determined (and changed) at runtime.
Delegation can be difficult when methods are interdependent.
Overriding: Code Inheritance [8/35] |
A subclass can inherit code from its superclass.
class B { void f() { ... } } class D extends B { void g() { ... } } class Main { public static void main(String[] args) { D x = new D(); x.g(); // executing code from D x.f(); // executing inherited code from B } }
Overriding: Hiding [9/35] |
A declaration is hidden if another declaration of the same name occurs in the scope of the outer declaration.
public class A { int x; static void z() { ... } } public class B extends A { float x; // old x still in scope, use super.x static int z() { ... } // old z still in scope, use A.z() byte x (char x) { // oh dear... char c = x; float f = this.x; int i = super.x; byte b = this.x(); } }
You should avoid hiding
http://developer.java.sun.com/developer/TechTips/2000/tt1010.html#tip2
Overriding: Overriding [10/35] |
Overriding: a non-static method in a subclass has the same name and signature as a non-static method in the superclass.
Intuition: the method implementation in the subclass ``replaces'' the implementation of the method in the superclass.
Example:
class B { public void m() { ... } } class D extends B { public void m() { ... } } B b = new B(); b.m(); // invoke m in class B D d = new D(); d.m(); // invoke m in class D
Overriding: Overloading versus Hiding [11/35] |
Unlike hiding, overriding can affect the behavior of a superclasses.
In the following example, the behavior of m is the same in both classes, using B.x in both cases.
class B { int x; void m() {...use x..} } class D extends B { float x; // ...inherit m... }
In the next example, the behavior of m is different in the two classes, since B.m will use B.f, but D.m will use D.f.
class B { int f() {...} void m() {...use f..} } class D extends B { int f() {...} //...inherit m... }
Overriding: Overloading [12/35] |
To override a method, the argument types must be the same in super and subclass.
If the types are different, the result is overloading not overriding.
file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package subclass.overload; class A { public void m(String s) { System.out.println("A"); } } class B extends A { public void m(Object o) { System.out.println("B"); } } class Main { private Main() {} public static void main(String[] args) { String y = "?"; Object x = y; B b = new B(); A a = b; a.m(y); // a.m(x); b.m(y); b.m(x); } }
Overriding: Method Dispatch (aka Method Resolution) [13/35] |
When an overridden method is invoked, we have to decide which implementation to run. There are two ways to do this: based on the static or dynamic type.
Recall
static refers to properties of the text of a program (synonyms are lexical, compile-time and early).
dynamic refers to properties of an execution of a program (synonyms are run-time and late).
declared type = compile-time type.
actual type = run-time type.
Both forms of method resolution proceed up the class hierarchy, using the superclass relation. In pseudo-code:
lookup(C,m) // return the address of code for method m if (C implements m) then return address of C.m else let B = the superclass of C return lookup(B,m)
Procedurally: if m is implemented in C then return the address of C.m else set C to be the superclass of C and repeat.
Static dispatch (early binding): start lookup with the declared type.
Dynamic dispatch (late binding): start lookup with the actual type.
Overriding: Instance methods [14/35] |
Instance methods [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package subclass.ex1; public class M { public static void main(String[] argv) { (new B()).m(); } } class A { void p() {System.out.println("A.p");} void m() { System.out.println("A.m"); this.p(); // static or dynamic binding? } } class B extends A { void p() {System.out.println("B.p");} }
Overriding: Class methods [15/35] |
Class methods [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package subclass.ex2; public class M { public static void main(String[] argv) { (new B()).m(); } } @SuppressWarnings("all") class A { static void p() {System.out.println("A.p");} void m() { System.out.println("A.m"); this.p(); // static or dynamic binding? } } class B extends A { static void p() {System.out.println("B.p");} }
Overriding: super [16/35] |
super [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package subclass.ex3; public class M { public static void main(String[] argv) { (new C()).m(); } } class A { void p() {System.out.println("A.p");} } class B extends A { void p() {System.out.println("B.p");} void m() { System.out.println("B.m"); this.p(); // static or dynamic binding? super.p(); // static or dynamic binding? } } class C extends B { void p() {System.out.println("C.p");} }
Why is it like this?
file:M.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package subclass.exB; public class M { public static void main(String[] argv) { (new B()).m(); // does this cause an infinite loop? } } class A { void m() {System.out.println("A.m");} } class B extends A { void m() { System.out.println("B.m"); super.m(); } }
file:M.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package subclass.exC; public class M { public static void main(String[] argv) { (new C()).m(); // does this cause an infinite loop? } } class A { void m() {System.out.println("A.m");} } class B extends A { void m() { System.out.println("B.m"); super.m(); } } class C extends B {}
Overriding: Method out of scope [17/35] |
Method out of scope [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package subclass.ex4; public class M { public static void main(String[] argv) { (new B()).m(); } } class A { private void p() {System.out.println("A.p");} void m() { System.out.println("A.m"); this.p(); // which p? } } @SuppressWarnings("all") class B extends A { private void p() {System.out.println("B.p");} }
Another example.
file:M.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
package subclass.exD; public class M { public static void main(String[] argv) { (new B()).m(); } } @SuppressWarnings("all") class B extends subclass.exD.otherPackage.A { public void p() {System.out.println("B.p");} }
file:A.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
package subclass.exD.otherPackage; public class A { void p() {System.out.println("A.p");} public void m() { System.out.println("A.m"); this.p(); // which p? } }
Overriding: Inner Classes [18/35] |
Dynamic or static? [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package subclass.ex9; public class M { public static void main(String[] argv) { O o = new X(); I i = o.getI(); i.m(); } } interface I { public void m(); } class O { void p() {System.out.println("O.p");} I getI() { //return () -> O.this.p(); return new I() { public void m() { O.this.p(); // static or dynamic binding? }}; } } class X extends O { void p() {System.out.println("X.p");} }
Overriding: Instance fields [19/35] |
Instance fields [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package subclass.ex6; public class M { public static void main(String[] argv) { (new B()).m(); } } class A { void p() { System.out.println("A.p: " + x); // which x? } final int x = 42; } class B extends A { void m() { this.p(); System.out.println("B.m: " + x); // which x? } final int x = 27; }
Overriding: Class fields [20/35] |
Class fields [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package subclass.ex5; public class M { public static void main(String[] argv) { (new B()).m(); } } class A { static void p() { System.out.println("A.p: " + x); // which x? } static final int x = 42; } @SuppressWarnings("all") class B extends A { void m() { this.p(); System.out.println("B.m: " + x); // which x? } static final int x = 27; }
Overriding: C++/C# [21/35] |
C++ and C# default to static dispatch, for all functions, including member functions.
Boxed entities are manipulated using
references/pointers, live java objects.
Unboxed entities are manipulated as values, like
java base types.
You can convert between them using wrapper objects.
Integer box(int v) { return new Integer(v); } int unbox(Integer r) { return r.intValue(); }
Java 1.5 does this automatically in most contexts.
In C#, instances of a class are boxed; instances of a struct are unboxed --- structs generalize Java's base types.
To get dynamic dispatch, use the keyword
virtual
, which is allowed only in classes
(not structs).
If you are interested in C#, see here: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csspec/html/vclrfcsharpspec_11_3.asp
In C++, objects of any type may be boxed or unboxed. For example:
string v = string("cat"); // unboxed string* r = new string("dog"); // boxed int v = 1; // unboxed int* r = &v; // boxed
To get dynamic dispatch, the receiving object must be boxed and the method must be virtual. See file:dynamicVersusStaticDispatch.cpp [source].
Overriding: Equals [22/35] |
Equals in a final class (non-extendable concrete class).
Return true if for each field _f
:
this._f.equals(that._f)
final public class C { public boolean equals(Object thatObject) { if (!(thatObject instanceof C)) return false; C that = (C) thatObject; return ...; } }
Equals in an extendable concrete class.
public class C { public boolean equals(Object thatObject) { if ((thatObject == null) || (thatObject.getClass() != this.getClass())) return false; C that = (C) thatObject; return ...; } }
CompareTo sanity: (x.compareTo(y)<0
implies
y.compareTo(x)>0
)
final public class C { public int compareTo(Object thatObject) { C that = (C) thatObject; return ...; } } public class C { public int compareTo(Object thatObject) { if ((thatObject == null) || (thatObject.getClass() != this.getClass())) throw new ClassCastException(); C that = (C) thatObject; return ...; } }
An example of the problem.
file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package subclass.equals; class C1 { int i; public C1(int i) { this.i = i; } public boolean equals(Object thatObject) { if (!(thatObject instanceof C1)) return false; C1 that = (C1) thatObject; return that.i == this.i; } } class D1 extends C1 { int j; public D1(int i, int j) { super(i); this.j = j; } public boolean equals(Object thatObject) { if (!(thatObject instanceof D1)) return false; D1 that = (D1) thatObject; return that.i == this.i && that.j == this.j; } } class C2 { int i; public C2(int i) { this.i = i; } public boolean equals(Object thatObject) { if ((thatObject == null) || (thatObject.getClass() != this.getClass())) return false; C2 that = (C2) thatObject; return that.i == this.i; } } class D2 extends C2{ int j; public D2(int i, int j) { super(i); this.j = j; } public boolean equals(Object thatObject) { if ((thatObject == null) || (thatObject.getClass() != this.getClass())) return false; D2 that = (D2) thatObject; return that.i == this.i && that.j == this.j; } } public class Main { public static void main(String[] args) { C1 x1 = new C1(27); C1 y1 = new D1(27,42); System.out.println("x1==y1? " + (x1.equals(y1) ? "true" : "false")); System.out.println("y1==x1? " + (y1.equals(x1) ? "true" : "false")); C2 x2 = new C2(27); C2 y2 = new D2(27,42); System.out.println("x2==y2? " + (x2.equals(y2) ? "true" : "false")); System.out.println("y2==x2? " + (y2.equals(x2) ? "true" : "false")); } }
Template Method: Some code [23/35] |
file:template/series1/Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package template.series1; interface Series { void next(); void print(); } class SeriesFactory { private SeriesFactory() {} public static Series newArith() { return new ArithSeries(); } public static Series newGeom() { return new GeomSeries(); } } class ArithSeries implements Series { int x; int y = 1; public void next() { x++; y = y+2; } public void print() { System.out.println("x=" + x + "; y=" + y); } } class GeomSeries implements Series { int x; int y = 1; public void next() { x++; y = y*2; } public void print() { System.out.println("x=" + x + "; y=" + y); } } public class Main { public static void main(String[] args) { Series x = SeriesFactory.newGeom(); x.next(); x.next(); x.print(); } }
Template Method: Using Delegation (Strategy) [24/35] |
file:template/series3/Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package template.series3; interface Series { void next(); void print(); } class SeriesFactory { private SeriesFactory() {} public static Series newArith() { return new ConcreteSeries(new OpAdd()); } public static Series newGeom() { return new ConcreteSeries(new OpMul()); } } class ConcreteSeries implements Series { int x; int y = 1; public void next() { x++; y = op.eval(y,2); } public void print() { System.out.println("x=" + x + "; y=" + y); } Op op; ConcreteSeries(Op op) { this.op = op; } } interface Op { public int eval(int x, int y); } class OpAdd implements Op { public int eval(int x, int y) { return x+y; } } class OpMul implements Op { public int eval(int x, int y) { return x*y; } } public class Main { public static void main(String[] args) { Series x = SeriesFactory.newGeom(); x.next(); x.next(); x.print(); } }
Template Method: Using Subclassing (Template) [25/35] |
file:template/series4/Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package template.series4; interface Series { void next(); void print(); } class SeriesFactory { private SeriesFactory() {} public static Series newArith() { return new ArithSeries(); } public static Series newGeom() { return new GeomSeries(); } } abstract class AbstractSeries implements Series { int x; int y = 1; public void next() { x++; y = this.eval(y,2); } public void print() { System.out.println("x=" + x + "; y=" + y); } abstract protected int eval(int x, int y); } class ArithSeries extends AbstractSeries { protected int eval(int x, int y) { return x+y; } } class GeomSeries extends AbstractSeries { protected int eval(int x, int y) { return x*y; } } public class Main { public static void main(String[] args) { Series x = SeriesFactory.newGeom(); x.next(); x.next(); x.print(); } }
Template Method: The Strategy Pattern [26/35] |
Describe a function in two parts: the part that varies goes in a separate class.
Here is a class diagram from the GOF:
Template Method: The Template Method Pattern [27/35] |
Describe a function in two parts: the part that varies goes in a subclass.
Here is a class diagram from the GOF:
Template Method: Template Method is NOT inheritance [28/35] |
Inheritance:
class B { void f() { ... } } class D extends B { void g() { ... f() ... } }
Template Method:
class B { void f() { ... g() ... } abstract void g(); } class D extends B { void g() { ... } }
Template Method: Hook Methods [29/35] |
Abstract method (mandatory hook method)
Hook method
Concrete method
Final method
Template Method: A Heirarchy of Abstraction [30/35] |
interface (aka, pure abstract class)
abstract class
concrete non-final class
final class
Advice: Make concrete classes final unless they are specifically designed to be subclassed.
Template Method: Interfaces and Abstract classes [31/35] |
Guideline: Always provide an interface. If you want to provide implementation for subtypes, you can additionally provide an abstract class:
package x; public interface I { void m(); void n(); } public class IAbstract implements I { abstract public void m(); // m not implemented public void n() { ... } // n implemented public protected void f() { ... } // f implemented protected } package y; public class IObj extends x.IAbstract { public void m() { ... f() ... } // use IAbstract's implementation of f } package main; public class Main { public static void main(String[] args) { x.I a = new y.IObj(); a.m(); a.n(); } }
Template Method: Template Example [32/35] |
file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package template.ftoc; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; /* This example is from Robert C. Martin */ public class Main { public static void main(String[] args) { (new FtoC()).run(); } } /* public */ abstract class Application { private boolean isDone = false; protected abstract void init(); protected abstract void idle(); protected abstract void cleanup(); protected boolean done() { return isDone; } protected void setDone() { isDone = true; } public void run() { init(); while (!done()) idle(); cleanup(); } } final class FtoC extends Application { private BufferedReader br; protected void init() { br = new BufferedReader(new InputStreamReader(System.in)); } protected void idle() { String fstring = readLine(); if (fstring == null || fstring.length() == 0) { setDone(); } else { double f = Double.parseDouble(fstring); double c = 5.0/9.0*(f-32); System.out.println("F=" + f + ", C=" + c); } } protected void cleanup() { System.out.println("FtoC exit"); } private String readLine() { try { return br.readLine(); } catch(IOException e) { return null; } } }
Template Method: Refactor to Strategy [33/35] |
file:Main.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package strategy.ftoc; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; /* This example is from Robert C. Martin */ public class Main { public static void main(String[] args) { (new App(new FtoC())).run(); } } final class App { private AppStrategy s; public App(AppStrategy s) { this.s = s; } public void run() { s.init(); while (!s.done()) s.idle(); s.cleanup(); } } /* public */ interface AppStrategy { public void init(); public void idle(); public void cleanup(); public boolean done(); } final class FtoC implements AppStrategy { private boolean isDone = false; private BufferedReader br; public boolean done() { return isDone; } public void init() { br = new BufferedReader(new InputStreamReader(System.in)); } public void idle() { String fstring = readLine(); if (fstring == null || fstring.length() == 0) { isDone = true; } else { double f = Double.parseDouble(fstring); double c = 5.0/9.0*(f-32); System.out.println("F=" + f + ", C=" + c); } } public void cleanup() { System.out.println("FtoC exit"); } private String readLine() { try { return br.readLine(); } catch(IOException e) { return null; } } }
Choosing Delegation [34/35] |
GoF: prefer delegation. Subclassing is too static -- forces awkward class hierachies -- inflexible.
/------------\ | Deal | \------------/ /_\ /_\ | | | | +------------+ +-------------+ | ActiveDeal | | PassiveDeal | +------------+ +-------------+ /_\ /_\ | | | | +------------+ +-------------+ | Tabular | | Tabular | | ActiveDeal | | PassiveDeal | +------------+ +-------------+
versus
/------------\ 1 /-------------------\ | Deal |<>----------------> | PresentationStyle | \------------/ \-------------------/ /_\ /_\ /_\ /_\ | | | | | | | | +------------+ +-------------+ +------------+ +-------------+ | ActiveDeal | | PassiveDeal | | TabularPS | | SinglePS | +------------+ +-------------+ +------------+ +-------------+
In english, we often say "is" when we mean "has the attribute of".
"The shirt is purple" versus "The shirt has a color attribute, which is purple".
"The room is a cuboid" versus "The room has a shape attribute, which is cuboid".
Choosing Subclassing [35/35] |
GoF: When a subclass overrides some but not all operations, it can affect the operations it inherits as well, assuming they call the overridden operations.
Delegation is difficult to code when strategies are interdependent.
For an example see Painting in AWT and Swing.
paint and update are related hook methods.
repaint is a non-final template method, which (indirectly) calls update.
public abstract class Component ... { ... public void paint(Graphics g) { } // Used for system-triggered painting public void update(Graphics g) { // Used for application-triggered painting paint(g); // default implementation } public void repaint() { // requests that the GUI thread call back to "update" at some point } }
By default, overriding paint affects repaint and update.
repaint and update can also be independently overridden.
In my opinion, paint should be abstract.
Revised: 2008/08/01 15:09