Assignment One Designs

Chris Dent
Matt Liggett
September 9, 2002

Partner review turned in.

These documents and associated code can be found at http://www.burningchrome.com/~cdent/649/ass1/stage/ass1design.html

HTML documents created by javadoc can be found at http://www.burningchrome.com/~cdent/649/ass1/stage/javadoc/.

See the Observer pattern on p 293 of the _Design Patterns_ text for
more on the Subject and Observer classes. In general the Observer
pattern is used when a group of potentially unrelated objects
(Observers) need to be kept aware of changes occurring in another
object (the Subject). Traditional methods would tightly couple the
Subject to the Observer. Observer is a more flexible method by which
Observers can register their interest in updates with the Subject.
When the Subject changes state it has a notify method that loops
through each of the registered Observers and updates them about the
change in state. Observers provide an update() interface. Subjects
provide attach() and detach() interfaces.

1.1

Overview An event channel with loose coupling between the state changing object and an object that observes the object changing state is the classic form of the Observer pattern (described in more detail in design 1.3). To make the coupling more loose than the standard form, an Intermediary class can provide a proxy between the state changing object and the object that wants to know about it. The proxy can be implemented as two linked Observer and Subject pairs. Such a system would allow for state changing objects to register with the Intemediary that they are available for observation. Objects curious about other objects that change state could register with the Intermediary to recieve notifications when objects change state. Investigation of the object that causes the update allows the observing object to determine what, if any, action is required as a result of the changing state. This design manipulates the Observer pattern somewhat to allow a Subject to request an Observer to register with it. This allows the intermediary to observe an arbitrary number of state changing objects. Interfaces The design is based on two Interfaces: Subject and Observer. Observer calls attach() on Subject to request that a reference to it be stored. When a change occurs on the Subject, its notify() method calls update() on each of the Observers. Classes Intermediary The Intermediary class implements both Subject and Observer. It is the Subject of StateObserver. It also Observes StateChanger. In addition to the usual Subject and Observer interfaces it also provides registerRequest(StateChanger) by which StateChanger asks Intermediary to observe it. StateChanger and StateObserver know of the Intermediary. Intermediary keeps a Vector of the StateObservers. StateChanger The StateChanger class implements the Subject Interface. At startup StateChanger asks Intermediary to observe it with registerRequest(). When it changes state, its notify() method calls update() on any Intermediaries that are observing it, passing a reference to itself. Intermediary calls update() on the StateObservers it knows about, passing along the reference to the StateChanger. The StateObserver can inquire with the StateChanger to determine if the state changer requires action by the StateObserver. StateObserver The StateObserver class implements with Observer interface. When it is updated by the Intermediary, it gets a reference to the StateChanger object through which it can get more information to decided if further action is required. A preliminary GIF of the design of this system is attached. The design has since been udpated. The GIF is included as evidence of the design process.

1.2

Overview From the assignment, we see that the desired behaviour is to have 4 different game elements operating concurrently on the screen. We can apply the Observer pattern twice to get the desired results. Subject 1: Button Button does not have to do anything except wait to be clicked. When it's clicked, Timer needs to know about it, so that it can start counting down 10 seconds. Hence, Timer will observe Button. Subject 2: Clock The Clock has a simple task: it has to tick off minutes and seconds. Given that it will already be marking the passage of time itself, it is trivial to have the Clock also call its notify() method periodically, to notify its Observers. Observers of Clock And who will be observing Clock? We know that Sprite has to move around the screen every 1/20 second. If Sprite observes Clock, and Clock calls notify() every 1/20 second, then Sprite will have its update() method called by Clock every 1/20 second. Within update(), the Sprite moves itself. Once Button has been pressed, Timer has to count down 10 seconds to zero. Therefore, Timer should be a _sometimes_ observer of Clock. Once Button is pressed, Timer's update() method will be called. At this point, it should call detach() on Button, and attach() on Clock (in that order). In this way, it will avoid any ambiguity about which object has called update() on it. After Timer has attach()ed itself to Clock, it will set its time remaining counter to 10 seconds and decrement that counter 1/20 second every time its update() method is called. When the counter reaches zero, the Timer now calls detach() on Timer and attach() on Button so that it can again wait for Button to be clicked. Why Timer has to attach and detach Why does Timer have to detach itself from Button and Clock? If Timer were not to detach itself before calling attach() on another Subject, it would not be clear, when Timer's update() method was called, which object had called it! Removing this ambiguity would require modifying the pattern slightly such that update() might take an Object argument, which would be a reference to the caller. This complicates things significantly. We find it simpler to have Timer watching only one Subject at a time.

1.3

(This is the problem for which we created code.) An initial UML Diagram GIF created with Poseidon started the design discussion which led to the attached code and the more complete UML diagram attached. The change in diagram represents the evolution of the design from initial sketches to a more complete understanding of the pattern. Overview A dynamic list of Integers and functions to report on the list as the list changes can be implemented using the Observer pattern. A class to contain the list of Integers has the role of Subject while classes that perform functions on the list act as Observers. To ensure a fair degree of flexibility and extensibility the design has 4 levels of class: Subject and Observer interfaces that are implemented by BasicSubject and BasicObserver classes; IntegerList that extends BasicSubject and ObservingFunctor that extends BasicObserver; and ReportSum and ReportLength (and other Report? classes) classes that extend ObservingFunctor. ListExerciser creates an IntegerList and ObservingFunctor children to exercise the system. Subject Provides the attach() and detach() interfaces. Both take an Observer argument. BasicSubject Adds a Vector to hold the list of observers that have registered with attach(). notifyObservers() takes an iterator of that vector to call update on the Observers. IntegerList Provides an interface for managing a list of Integers that are stored in a Vector. Provides a public interface for add(), remove(), size(), iterator(). When add() or remove() are called, notifyObservers() is called. Observer Provides the update() interface. BasicObserver Keeps a reference to the Subject being observed. Provides methods to attach() to and detach() from the Subject. ObservingFunctor Takes a reference to the IntegerList and makes that the Subject. When an update() is received report() is called. Report outputs the String created by generateReportString. generateReportString() is passed the Iterator from the IntegerList to perform operations. (Later experimentation revealed that some children of the ObservingFunctor may wish to iterate over the IntergerList more than once. An Iterator cannot be reset, so a second Iterator must be created within generateReportString by asking for it from IntegerList. This is a bit ugly. An example of where this might happen is in a ReportStandardDeviation functor. A different way around the problem could be to host common functions, such as mean(), on ObservingFunctor, but that also seems a bit ugly.) ReportSum As an example of a working functor, ReportSum iterates through the IntegerList iterator to add each Integer to an growing int sum. Consequences Because iterators return Objects care must be taken to cast the objects returned by Iterator.next() to Integer. Similarly, because BasicObserver defines the subject member variable as a Subject, it must be cast to IntegerList when attempting to call methods on that object. These include iterator() and size().

1.4

Overview The problem is to keep a remote client aware of the current state of a changing set of files. We propose 3 classes: FileSet, FileServer and FileServerSession. FileServer shall be the main entry point. It will spawn a single FileSet instance to run as a thread, polling a directory every second to see if its contents have changed. It will then create a new FileServerSession object (which implements Runnable) for each new socket connection it accepts. A new thread will then be kicked off for the FileServerSession. The Observer pattern is used between the FileSet (Subject) and each FileServerSession (Observer). The FileSet notifies all observers whenever the set of files has changed. A UML Diagram is attached. FileSet FileSet's run() method need only set up a loop in which it polls a directory periodically, and if the contents of that directory have changed, it calls notifyObservers(). So that the list (or vector or whatever) of observers is not changed while it is in use, the methods attach(), detach() and notifyObservers() should all be declared synchronized. FileServer FileServer has a simple job: call ServerSocket.accept() in a loop, then create new threads running new FileServerSession instances each time accept returns. FileServerSession The run() method of FileServerSession should first get the latest list of files from FileSet then send it across the Socket. Next, it should attach() itself to FileSet. After this, the thread essentially needs to "go to sleep" until the set of files changes. This can be accomplished with synchronized(this) { wait(); } That causes the current thread to yield until some other thread calls the notify() method on the FileServerSession object. Now, the body of update() should consist solely of: synchronized(this) { notify(); } Note that notify() is called from the FileSet's thread. When this executes, the FileServerSession thread wakes up. Therefore, if we simply write the following loop in FileServerSession's run() method: do { synchronized(this) { wait(); } } while (writeFileList()); then the thread will continue to re-write the file set across the socket each time the file set changes, exactly the desired behavior. Note that writeFileList() returns a boolean, and it returns false if there are any communication problems, so that run() will return if the connection is broken or closed by the other side. Protocol Note that the protocol defined here is extremely simple. Clients connect to a server and are immediately given a list of available files. Whenever the list changes, they are given an updated list. There is no explicit query on the part of the client, but it will always be kept up to date.