Feb 21, 2005

Pattern Patter: Anonymous Subclass with Instance Initializer

In JUnit Recipes, JB Rainsberger points out this idiom:

static final Set s = new HashSet() {{
  add("this");
  add("that");
  add("another");
}}; 


(JB points to an article by Paul Holser, which cites Dave Astels’ Test-Driven Development as the source.)

What’s it do? The new HashSet(){}; part creates an anonymous subclass (a new type of HashSet without any name). The inner braces are an instance initializer, run before the constructor (implicit and empty) of our new class. So this code creates a new Set, and fills its contents.

Sep 15, 1999

Pattern Patter: Hide Information in Plain Sight

By B. Douglas Wake and William C. Wake

Summary: Sometimes we have information that is potentially useful or important, but is not normally of interest. This pattern discusses when and how to hide this information in plain sight.

Context

  • Information is designed using a high format and a powerful (often new) tool.
  • Information is viewed in a low format by a less powerful (often previously existing) tool.

Forces

  • Translating from high to low form loses information - we can't easily translate back from low to high form.
  • The high form is retained for future modification.
  • The low form may be treated as read-only.

Resolution

"Hide" high-form information in the low-form object, in such a way that the low-form tool or the user can ignore the hidden information.

Discussion

The low form is read-only, because low-level tools will not know enough to manipulate the information in the high form. (If they did, they'd be high-form tools in disguise.) The hidden information need not be a complete replication of all high-form information; it might be just enough information to locate the high-form original.

Examples

  • Netscape Navigator. (See http://www.netscape.com). Navigator's page can be set up to display the URL on the printed page. Then, the printed form is the low form, but it contains the URL that can be used to locate the high form.
  • MIME mail. (N. Freed and N. Borenstein, RFC 2045, "Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies," November, 1996.) The MIME data format allows for complex mail messages that contain alternative formats for a single message. A complete MIME mail reader might support display of all forms of a message. A mail reader with minimal MIME support might display the plain ASCII version of a message, and ignore the other forms. A mail reader unaware of MIME might display all parts of the MIME message (which would include the simple ASCII form).
  • Digital watermarks. (Ref??) A museum with images might freely distribute low-resolution versions of its images, including a visible watermark that identifies the source of the image. The museum might sell a high-resolution version containing an invisible watermark that would allow detection of illegal re-distribution of the image.
  • XEROX PARC's XAX system. (W. Johnson et al., "Bridging the paper and electronic worlds: The paper user interface," Proceedings of INTERCHI, 1993, pp. 507-512, ACM, April, 1993.) The XAX system encodes information about a document in glyphs that are "not visually distracting when embedded in a paper document." This lets them move from the low-level paper form up to high-level structured electronic forms.
[Originally written November, 1997.]

Aug 1, 1999

Pattern Patter: Renderer

Summary: Use renderers to show sub-views.

Context

  • You've separated the model of a complex object from its view (as in Observer or Model-View-Controller).
  • The bulk of the view doesn't change much.
  • Variable items ("cells") in the model may require a specialized sub-view.

Solution

Divide the view into two parts. Let the main view display the "frame" (the parts that tend not to vary); let the main view also maintain a reference to a "renderer" that can display each repeated (variable) part.
The renderer typically has a small interface (usually just one method), that takes as its arguments all information needed to display one cell.
Typically, each view will have a default renderer, that takes the object to display, calls toString() on it, and displays that. This way, the default view is often good enough for a first cut.

Uses

  • This is a special case of the Observer or Model-View-Controller pattern.
  • The Swing libraries for Java use this approach for lists, tables, and trees.
  • The Swing libraries use a related idea ("editor") for editing cells of these objects.

Jun 12, 1999

Growing Frameworks: White-Box and Black-Box

Not all frameworks are alike. One way to distinguish between them is the notion of white box vs. black box.

A white-box framework requires the framework user to understand the internals of the framework to use it effectively. In a white box framework, you usually extend behavior by creating subclasses, taking advantage of inheritance. A white box framework often comes with source code.

A black-box framework does not require a deep understanding of the framework’s implementation in order to use it. Behavior is extended by composing objects together, and delegating behavior between objects.

A framework can be both white-box and black-box at the same time. Your perception of how “transparent” a framework is may depend on non-code aspects such as documentation or tools.

Frameworks tend to change over their lifetime. (See [Johnson & Roberts].) When a framework is new, it tends to be white-box: you change things by subclassing, and you have to peak at source code to get things done. As it evolves, it becomes more black-box, and you find yourself composing structures of smaller objects. Johnson and Roberts point out that frameworks can evolve beyond black-box, perhaps becoming visual programming environments, where programs can be created by interconnecting components selected from a palette. (JavaBeans is an effort in that direction.)

Visual environments and even black-box frameworks sound so much easier to use than white-box frameworks - why would we ever bother creating white-box frameworks in the first place? The basic answer is “cost”. To develop a black-box framework, we need a sense of which objects change the most, so we can know where the flexibility is most needed. To develop a visual environment, we need even more information: we need to know how objects are typically connected together. Discovering this costs time. White-box frameworks are easier to create and have more flexibility.

White-Box Frameworks

The most common sign that a framework is white-box is heavy use of inheritance. When you use the framework by extending a number of abstract (or even concrete) classes, you are dealing with a white-box framework. Inheritance is a closer coupling than composition; an inherited class has more context it must be aware of and maintain. This is visible even in Java’s protection scheme: a subclass has access to the public and protected parts of the class, while a separate object only sees the public parts. Furthermore, a subclass can potentially “break” a superclass even in methods it doesn’t override, for example by changing a protected field in an unexpected way.

What are the effects of an inheritance-based approach?
·         We need to understand how the subclass and superclass work together.
·         We have access to both the protected and the public parts of the class.
·         To provide functionality, we can override existing methods, and implement abstract methods.
·         We have access to the parent’s methods (by calling super.method()).

Example: A simple applet
      import …
      public class MyApplet extends Applet {
            public void paint(…) {…}       //TBD??
      }
Notice how we depend directly on the superclass, even to using its methods freely.

A subclass is coupled to its parents, and we deal with the benefits and costs of that fact.

Black-Box Frameworks

Black-box frameworks are based on composition and delegation rather than inheritance. Delegation is the idea that instead of an object doing something itself, it gives another object the task. When you delegate, the object you delegate to has a protocol or interface it supports, that the main object can rely on.

In black-box frameworks, objects tend to be smaller, and there tend to be more of them. The intelligence of the system comes as much from how these objects are connected together as much as what they do in themselves.

Composition tends to be more flexible than inheritance. Consider an object that uses inheritance versus one that delegates. With inheritance, the object basically has two choices: it can do the work itself, or it can call on the parent class to do it. In a language like Java, the parent class is fixed at compile time.

With delegation, an object can do the work itself (or perhaps in its parent), or it can give the work to another object. This object can be of any useful class (rather than only the parent), but it can also change over time. (You can delegate to an object, change the delegate to be another object, and use the new object as the delegate next time.)

Example: TBD

[TBD compare to cutting grass - children vs. lawn service?]


Converting Inheritance to Composition

In “Designing Reusable Classes,” Johnson and Foote identify this rule:
Rule 12. Send messages to components instead of to self. An inheritance-based framework can be converted into a component-based framework black box structure by replacing over-ridden methods by message sends to components.

Let’s apply their advice to an example. Suppose we’ve got a class that sues the Template Method design pattern like this:
public class ListProcessor {
      final public void processList() {
            doSetup();
            while (hasMore()) {
                  doProcess1();
            }
            doTeardown();
      }
 
      protected void doSetup() {}
      protected void doTeardown() {}
      abstract protected boolean hasMore();
      abstract protected void doProcess1();
      // …
}
 
with this as a typical subclass:
 
public class TestProcessor extends ListProcessor {
      int i = 0;
      protected boolean hasMore() {return i < 5;}
      protected void doProcess1() {System.out.println(i);}
}
 
In the simplest form of the transformation, we can take each method designed to be subclassed, and whenever it is called, replace it by a message to the delegate. We’ll also add some code to set up the delegate.
 
public class ListProcessor {
      ListProcessor delegate;
 
      public ListProcessor(lp) {delegate = lp;}
 
      final public void processList() {
            delegate.doSetup();
            while (delegate.hasMore()) {
                  delegate.doProcess1();
            }
            delegate.doTeardown();
      }
 
      protected void doSetup() {}
      protected void doTeardown() {}
      abstract protected boolean hasMore();
      abstract protected void doProcess1();
      // …
}
 
We can extend this like before. [TBD] You might create it like this:
      ListProcessor lp = new ListProcessor (new TestProcessor());
 
Compare how these two situations look at run-time:
                [LP] ß [Test]            =>             [:Test]
 
                [LP]odelegate             =>     [:LP] --- [:Test]
                ^
                [Test]
 
So far, this doesn’t seem to be worth the trouble: ListProcessor already has a copy of “processList()”; it doesn’t need the one in Test.
 
The next step is to introduce a new class for the delegate, restricted to just the capabilities it needs. The methods called on the delegate define its protocol. We could handle this via an abstract class, but I prefer to use a Java interface:
public interface ListProcessAction {
      void doSetup();
      boolean hasMore();
      void doProcess1();
      void doTeardown();
}
 
These are the methods intended to be over-ridden.
 
Now the main class can use the ListProcessorAction for its delegate. Furthermore, as ListProcessor is no longer intended to be subclassed, it no longer has any need for those protected methods:
 
We could make our class depend on this interface:
public class ListProcessor implements ListProcessorAction {
      ListProcessorAction delegate;
 
      public ListProcessor(ListProcessorAction a) {delegate = a;}
 
      final public void processList() {
            delegate.doSetup();
            while (delegate.hasMore()) {
                  delegate.doProcess1();
            }
            delegate.doTeardown();
      }
 
      // …
}
 
with this concrete implementation of the action:
public class ConcreteAction {
      int i = 5;
      public void doSetup() {}
      public boolean hasMore() {return i < 5;}
      public void doProcess1() {System.out.println(i);}
      public void doTeardown() {}
}
 
 
[TBD: Typically when these involve abstract methods, you might create an abstract class version, which will be extended by the end class. Or if the protocol is small and completely abstract, you don’t need concrete classes.]
 
The structure looks like this:
                [LP] -delegate- [<<interface>> LPA]    =>    [:LP] -delegate- [:CA]
                                             ^
                                                [ConcreteAction]
 
This runtime structure is similar to the previous one, but now ListProcessor and ConcreteAction are separate classes.
 
We have split one big object, that knew both the algorithm and the individual steps, into two classes, one for each concern. Look at the tradeoffs. In the initial version, everything was in one place. To trace the new version, you have to understand the delegation structure and how it can vary at runtime. When you write a new action, it’s easier to focus on it in isolation, but harder to see how it fits into the big picture.
 
See how the design has changed:  [TBD]
[//TBD]
 
 

Step by Step

This is a systematic description of how to convert
                [Base]     to    [Base] –delegate-- [<<int>>Action] <- [ConcreteAction]
 
Cautions: [TBD]
·         Calls to super(). (Need to work through ramifications.)
·         Recursion. (Need to eliminate or understand.)
 
This approach follows a re-factoring style, moving in small steps and letting the compiler do the work. (See [Fowler].)
 
1.       Create a new interface.
      public interface Action { }
(name it appropriately).
 
2.       Create a new class implementing this interface:
      public class ConcreteAction implements Action {}
 
3.       In Base, add a delegate field, and modify each constructor to take an Action as a parameter; use this to set the delegate:
      protected Action delegate;

      public Base (Action a) {
            delegate = a;
            // rest as before
      }
     
 
4.       Each protected method in Base is presumably called in Base. For each such method:
·         Move the signature to Action and change it to “public”
·         Move the routine itself to ConcreteAction (and make it public there as well).
·         Replace each call to “method()” with “delegate.method()”.
 
For example,
                Base {…
                                protected method() { /*impl*/.}
                                … method(); …
                }
 
becomes
                Base {… delegate.method(); … }
                Action { … public method(); …}
                ConcreteAction {… public method() {/*impl*/} … }
 
Note: You can find the call sites by temporarily changing the method name to “xxx”, and seeing what breaks in base.
 
Moving protected methods may force you to pull over some private methods as well, or perhaps maintain a reference back to Base. Unfortunately, this is not a fully mechanical process. Similarly, if Base’s methods involve recursion or calls to super(), you will need insight into how the class works and how you want to split it.
 
5.       Check whether methods in Base call any of Base’s public methods. If so,
·         Copy the signature to Action
·         Copy the method to ConcreteAction
·         Replace the method body in Base with “delegate.method()”.
 
Again, be aware of private methods, recursion, and super().
 
6.       Polish things up: get Base, Action, and ConcreteAction to compile properly.

7.       Check out any subclasses of Base. Figure out whether they should remain a subclass of Base, become a subclass of ConcreteAction, or become an independent action implementing the Action interface. (The class may need to be split.) Distribute the subclass’ behavior where it should go. As usual, be careful about recursion and super().
 
8.       Find each call to a constructor for Base or its subclasses. (Let the compiler tell you where they are.) Add a new parameter to the constructor, “new ConcreteAction()” where Base wants it.
 
 
 

Conflicting Class Hierarchies

Java only supports single inheritance, but sometimes you find yourself wanting multiple inheritance. You can use interfaces and delegation to help in many such situations.
 
Look at java.util.Observable. In some ways, it could be a basis for an implementation of the Observer design pattern. However, the fact that it is a class and not an interface is a flaw.
 
Suppose you have a Vector, and you’d like it be Observable (perhaps so a listbox widget can watch it for changes). Because both Vector and Observable classes are already classes, you’d like this:
                [Vector] <- ObservableVector -> [Observable]     // MI => !Java
 
Suppose Observable were an interface instead. A class can implement as many interfaces as it needs to, so we could do this:
                [Vector] <- ObservableVector - - - > [<<int>> Observable]    // Legal Java but not JDK 1.x
 
[TBD - double-check against JDK]
 
There’s a reason Observable is a class though: it maintains machinery for notification. If we had a convenience implementation, we could delegate to it.
 
                [Vector] <- ObservableVector -- -- > [<<int>> Observable]
-- delegate -- [ObservableHelper]
If Java had multiple inheritance, one class could cover both the Vector behavior and the Observable behavior. Without multiple inheritance, we can get the effect by connecting together a pair of classes.
 
[TBD: How class can we get to the 1.1 Listener event model?]
 
The Swing library designers faced this problem. Their solution is to ignore Observable, and instead create a ListModel that models the basics of vector handling. In Swing, there is an AbstractListModel, that handles notification, and can delegate to a concrete vector or list class.
 
 

Inner Classes for Multiple Inheritance

Sometimes a class implements several interfaces when it would be better served by using Java's inner classes. Consider this example:
 
                public class MyPanel extends JPanel implements MouseListener {
                                JButton b1 = new JButton("First");
                                JButton b2 = new JButton("Second");
                               
                                public MyPanel() {
                                                b1.addActionListener(this);
                                                add(b1);
                                                b2.addActionListener(this);
                                                add(b2);
                                }
 
                                public void actionPerformed(ActionEvent e) {
                                                if (e.getSource() == b1) {
                                                                System.out.println("b1 action");
                                                }  else {                          // assume b2
                                                                System.out.println("b2 action");
                                                }
                                }
                }
 
Here, MyPanel acts both like a JPanel and an listener. Notice the code for actionPerformed(): it’s got an ugly “if” statement that’s practically a case statement. Such a construct is a sign that we’re not as OO as we could be, and we can move intelligence around.
 
We’ll use a pair of inner classes, cleaning up MyPanel a bit. This keeps it from receiving unnecessary notifications, and avoids the need for a test to see which button was clicked.
 
                public class MyPanel extends JPanel implements MouseListener {
                                JButton b1 = new JButton("First");
                                JButton b2 = new JButton("Second");
                               
                                public class FirstListener implements ActionListener {
                                                public void actionPerformed(ActionEvent e) {
                                                                System.out.println(“b1”+e);
                                                }
                                }
 
                                public class SecondListener implements ActionListener {
                                                public void actionPerformed(ActionEvent e) {
                                                                System.out.println(“b2” + Date.getTime());
                                                }
                                }
 
                                public MyPanel() {
                                                b1.addActionListener(new FirstListener());
                                                add(b1);
                                                b2.addActionListener(new SecondListener());
                                                add(b2);
                                }
 
                }
 
The first version was more in the style of JDK 1.0.2, where the event detection hierarchy had to match the container hierarchy. The second version is more in JDK 1.1 style.
 
You can carry this a step further to use anonymous inner classes:
                public class MyPanel extends JPanel implements MouseListener {
                                JButton b1 = new JButton("First");
                                JButton b2 = new JButton("Second");
                               
                                public MyPanel() {
                                                b1.addActionListener(new ActionListener() {
                                                    public void actionPerformed(ActionEvent e) {
                                                                System.out.println(“b1”+e);
                                                    }
                                                });
                                                add(b1);
 
                                                b2.addActionListener(new ActionListener() {
                                                    public void actionPerformed(ActionEvent e) {
                                                                System.out.println(“b2” + Date.getTime());
                                                    }
                                                });
                                                add(b2);
                                }
 
                }
 
Many people might find this on the edge of readability - some on the near side and some on the far side.
 

Decorator and Accumulating Functionality

[TBD]
 
 

Interfaces and Plug-Compatibility

[TBD]
 
 

Type Generality

 
 
 

Miscellaneous

[TBD]

“Rule 6. The top of the class hierarchy should be abstract.”


Responsibility-Driven Design Tutorial, #21, OOPSLA ’98, R. Wirfs-Brock & Alan McKean [TBD]

Slide 81: When factoring, favor simplicity
From simple to complex:
Parameterizing a method
Simple condition checking & decisions within a method
Delegation to a pluggable object [Java interface - template method]
Classification and inheritance
Dynamic parsing & interpretation

Slide 84: Inheritance and Hot Spots
                Configurable Algorithm Design
                Tag steps as replaceable
                Send messages to self
                Template method: Entire algorithm is final.

end Wirfs-Brock

Jun 11, 1999

Growing Frameworks: Miscellany

Philosophy
                Interfaces
                Generic
                Hotspots
 
Graph
Workflow
Reporting
Drawing
 

Graph

                G = <N,E>
                N & E = 0
                E = <N,N>
 
Graph - Node - Edge
 
================================
                                Graph                    
 
1.             Graph
                Enumeration getEdges()
                Enumeration getNodes()
                boolean isMember(Node)
                boolean isMember(Edge)
                int getEdgeCount()
                int getNodeCount()
 
                Node
                Object getUserObject()
                void setUserObject()
 
                Edge
                Object getUserObject()
                void setUserObject()
                Node getFromNode()
                Node getToNode()
 
2.             Node
                Enumeration getIncomingEdges()
                Enumeration getOutgoingEdges()
 
3.             Graph
                boolean isValid()
 
4.             Graph
                Mutable
                addNode(Node)
                removeNode(Node)
                addEdge(Edge)
                removeEdge(Edge)
 
                Edge
                Mutable ??
                                setFromNode()
                                setToNode()
 
5.             Node
                                inNodes
                                outNodes
 
6.             Graph
                                throws??
 
 
Decisions -
* Add edges to the nodes or the graph?
* Should createNode() and createEdge ()  be factory methods on Graph?
* Implementations - Node could have list of outnodes (with implicit edges), or perhaps independent nodes & edges in a bitarray.
 
Graph Applications:
                Workflow
                                Bug-tracking
                                Trouble tickets
                                Student registration
 
 

DRAWING TOOL

 
Figure
                bounds
                position
                draw
                dependents
                contains
 
Handle
 
Canvas
 
Tool

 

BACKGROUND THINKING

 
Frameworks in the small:
* Interface - trace specs   Hoffman??
* Specsmanship - model
* Parnas
* Booch
* Meyer
 
 

FINAL GRAPH

 
Node
                Object getUserObject()
                void setUserObject(Object o)
                Enum getInEdges(), getOutEdges()
                Enum getInNodes(), getOutNodes()
 
Edge
                Node getFromNode()
                Node getToNode()
                Object getUserObject()                       *
                void setUserObject(Object o)            *
                                * implications for independent representation
 
Graph
                Enum getEdges()
                Enum getNodes()
                int getEdgeCount()
                int getNodeCount()
                bool isMember(node)
                bool isMember(edge)
                bool isValid()
 
MutableGraph -  subtype
                createNode([uo])
                createEdge([uo])
                removeNode(n)
                removeEdge(e)
                                throw graph change exception??
 

OUTLINE

 
I. What is a framework?
                Philosophy - generic, interface, hotspots
                definitions
 
II. Graph - mini-framework
                Domain - sample uses
                Dimensions of flexibility
                First few cuts - multiple implementations
                Frameworks in the small
                Final framework - code
                Sample uses
                                web checker
                                traffic simulator
                                UML Diagrammer
                Use of packages
 
INTERLUDE: Frameworks in the small
 
III. Patterns and Swing
 
IV. Workflow
                Layered framework
                model
                framework
                design patterns - policy vs implementation, strategy
                uses
                                bug tracker
                                student registration
                                trouble tickets
 
V. Reporting
                Predicate / Result
                Groups
 
VI. Drawing
                Separate framework
                Connect to graph
 
VII. Assembly
 
VIII. Growth, Delivery, Engineering
 

HOFFMAN - FRAMEWORKS in the SMALL

 
* Consistent: "with partial knowledge, the remainder of the system can be predicted"
* Essential - omit needless features
* General: Open-ended (future expansion) and complete (all functions of class)
* Minimal - independent features are separate
* Opaque - information hiding. Each module hides a secret. The module needn't change its interface even if the secret changes.
 
Application:
                Pop combines pop and top - bad
                ungetc combines get  and advance - bad
                graph - node & edge deletion
                combining get/set generally bad
 
Graph interface
Function                       Input    Output       Exceptions
s_init      int           -               maxnodes
g_numnodes         -               int
----------------------------------------------------------
s_addnode            int           -               nodenum, exnode
s_delnode             int           -               notexnode
g_exnode               int           bool       
----------------------------------------------------------
s_addedge            int           -               exedge, len
                int          
                real
s_deledge              int           -               notexedge
                int
g_exedge               int           bool       
                int
g_edgelen             int           real          notexedge
                int
----------------------------------------------------------
g_expath                int           bool
                int
g_pathlen              int           real          notexpath
                int
g_firststep             int           int           notexpath
                int
--------------------------------------------------------
Note: ex=exist, g=get, s=set                              
 
 
 
 

Abstract Data Types

Completeness
                Each method defined for each representation
 
Consequence
                Does it accept null?
                Does it return null?
                Can args have illegal values eg out of range?
 
 

WHAT IS A GRAPH?

 
We aren't doing this to provide a pure mathematical graph: we're doing it so we can apply the idea of a graph to real problems. This leads us to other considerations:
 
* We have information to attach to nodes and edges
 
* Edge from node to itself?
 
* Do we want multiple occurrences of an edge? (Classical def doesn't allow it.)
 
* Are our graphs mutable or immutable? We might like to distinguish these cases.
 
* Do we want constraints on our graph? (eg connected or non-cyclic)
 
 
From our sample domains, we see the need to handle these cases:
* We need to attach information to both nodes and edges. Every application we examined could take advantage of this.
* We will allow multiple occurrences of edges. When an edge has extra information, it's not merely recording connectivitiy, it's attaching ther info to the edge, and many sets of information could connect two nodes.
* Mutable/immutable is a useful distinction. (Example jdk1.2 collections) Many situations may have an expensive construction stage, followed by read-only use.
* Validity of the graph is important. This may be useful in allowing for multiple implementations. Some can take advantage of limited graph structure.
 
 
 
APPROACH
Graph class
                Develop class - subclass to use - critique interface
 
From class to framework
                Move to interface - black-box via adding userObject
 
HOFFMAN _ BAD GRAPH
 
Function                       Input    Output       Exceptions
s_init      int           -               maxnodes
g_numnodes         -               int
g_inf       -               real                          *1
s_addedge            int           -               nodenum, len, src_q_dst
                int          
                real
g_visited               int           real                          *2
                int
g_pathlen              int           real
g_firststep             int           int           notexpath
                int
g_edgelen             int           real          notexedge              *3
                int
 
*1  Infinity > any path length
*2  True if edge for node
*3  Length s to d, or g_inf if no path.
 
Notes: Assumes edge of length 0 between any node and itself.
Method g_first_step returns the first node in the shortest path to d. Pass in the previous result to compose a path.
 
Problems:
1. Can't add node without edges present
2. "self-loop" on node is a pain
3. Pathlen - can't find edge without also finding the path
4. No deletion except s_init
 
 
 
 
 
 
 
 
 

GRAPH CLASS - NOTES

 
Now - this is not a terrible class.
But there are things that can be improved.
 
Three things stand out:
1.
2. The "name" field is in all subclasses - but some won't need it. Furthere - it's there as a public field - it should at least be wrapped. Probably best to remove it completely.
3.
 
Guidelines:
Don't make subclasses pay for extra baggage
 
List of nodes:
Let's make them enumerations.
String name - remove it. Let subclasses add it if they need it.
 
// v0.2
public class Node {
                private Vector nodes;
                public void addEdge (Node n)
                public void removeEdge(Node n)
                public Enumeration getNodes()
                public Enumeration pathTo(Node n)
}
 
 

PULLING OUT THE ABSTRACTION

 
The graph case is "easy", because we have the mathematical model to fall back on.
 
First, we see there is a class for Nodes, but none for the graph as a whole, so we'll add
                public class Graph {
                                public Enum getNodes()
                                public void addNode(Node n)
                                public Enum getNodeCount()
                                public void removeNode(Node n)
                }
 
Second, we may want edges to be explicit. This may make our overall representation a little more expensive, but we have information we need to associate with edges.
 
                public class Edge {
                                public Node getFromNode()
                                public Node getToNode()
                                Edge(Node n1, Node n2)
                }
 
We'd like to be able to look at all edges in the graph:
                getEdges()
                getEdgeCount()
 
Where do we add edges - the node or the graph? Probably want to add them to the graph.
                add(edge)
                remove(edge)
                add(node)
                remove(node)
 
Multiple implementations are a consideration.
 

GROWING INTO A FRAMEWORK

 
* Indepdent of implementation - strong barrier
 
* Inversion of control (vs library)
 
* Extensible - black-box
 
 
 
 
 

IDEAS

* Adapter classes
* Frameworks
* Pree - Hot spots
 

GUIDELINES

 
Rules - Interface
* Avoid getter/setter functions
* Look for missing abstractions
* Pre/post-conditions
* Defined behavior for all arguments (eg null)
* Consistency
* Independent features are separate (minimal)
* What's the secret?
* Don't expose attributes
* Essential: omit needless features
* General: open-ended and complete
 
Closed set - native types or frameworks'
 
Guidelines - framework
* Abstract classes
* Black-box, white-box
* Library
* Flow of control
* Hot spots
* Don't make subclasses pay for what they don't use (cost ala carte)
* Implementation -independent
* Class relationships to base class
* Seek models - steal ideas
 
Patterns in frameworks
* Template method
* Factory
* Composite
* Decorator
* Strategy
 
Lifetimes
* Expand/contract
* Release
* Testing
 

PATTERNS IN AWT

 
MVC/Event-Observer
                Event mechanism
                PLAF
 
Composite
                JComposite -> Container
               
Flyweight
                Border
 
Decorator
                Border (Compound Border)
 
Template Method
                JComponent paint, paintComponent, etc.
 
Strategy
                LayoutManager
 
Factory Method
                DateFormat
               
Interpreter
                SimpleDateFormat
 

 

REPORTS

Query
 
Format
 
Accumulators
                By type
                By date
                By page
 
 

References

 
Schmidt, Douglas. Using Design Patterns to Develop Reusable Object-Oriented Software. ACM Computing Surveys, 28(4), December, 1996.
 
J. Vlissides. Subject Oriented Design. C++ Report, Feb., 1998.
 
Harrison & Ossher. Subject-Oriented Programming. OOPSLA '93.
 
 
                                Testing OO
 
R.M. Poston. Automated Testing from Object Models. CACM Sept'94, v37 #9, pp 48-58.
 
G. Rothermel & M.J.Harrold. Selecting Regression[s] Tests for Object-Oriented Software. Proc Conf Software Maintenance, IEEE, 1994, pp 14-25.
 
D.E. Perry & G.E. Kaiser. Adequate Testing and Object-Oriented Programming. JOOP 1990 2(5) pp 13-19.
 
Sept '94 CACM. Special issue on object-oriented testing.
 
Jacobson. OOSE.
Marrick. The Craft of Software Testing.
McGregor. Object-oriented Software Development.
Siegel. Object-Oriented Software Testing.
 
Object Magazine. Pattern Language for Testing Object-Oriented Software. V5 #8 Jan'96. pp 32-38. "PLOOT" - Firesmith.
 
CACM Oct. '97 - Special issue on frameworks
 
Framework-Based Software Development in C++
                Gregory F. Rogers, Jan. '97
 
CACM Oct' 96
                Special issue on patterns

References
 
Online (URLs)
 
Books
 
Articles
 
 
 
Winograd/Flores
                Reflection in Action
 
The Reflective Practitioner
                Donald Schoen
 
 
 
 

Ideas

 
Frameworks vs class libraries
 
[What's the difference?]
 
 
 
Show dynamic models for designs
 
 
 
Beans as a framework
 
 

Workflow

 
(Idea for another framework)
 
Entities
Processes
Constraints
"Who's next?" - workflow engine
Measurement
Return to sender
State machine?
Aging
Off the clock
 
 
 
Reports:
Time in node
Time in cycle
Close time (clock, off-clock)
By node, etc.
Predicates
Recursion?
 

JotDraw

 
Fast interaction
 
Links count (move objects => links adjust)
 
Fast
 
                X  for shapes  [buttons]
 
                Auto-scroll for typing
 
Keystrokes for shapes
 
Auto resize
 
Zones (tab key)
 
Click to drag or delete
 
Drag bomb over node to kill?
 
 
 
Issues
 
Conflicting Class Hierarchies
 
 
Separation of concerns
 
 
Coupling & cohesion
 
 
Separate policy & implementation
 
Explain why it's better (rules)
 
 
 
 
Threads
 
 
Corba
 
 
Applet as an example of template method
 
 

Putting it all together - design of a searching framework.