|
|
|
|
|
|
|
|
class Person {
private String name;
private String location;
private int age;
...
public Person(String name) { setName(name); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getAge() { return age; }
public void setAge(String age) { this.age = age; }
public String print() { ... }
...
}

Assume now that we want to add little more information, but only to those people employed as scientists. Here, the Decorator Pattern [GHJV95] provides a good means to introduce a role object to particular Person objects like shown in figure 1. The person-specific "state" of a Scientist, that is name, location and age, is accessed via forwarding methods that call the respective accessor methods defined in class Person. Furthermore, this allows us to declare Scientist as a subclass of Person and thus to use instances of the former class whenever the type of the latter is expected. However, note that a scientist is not represented as a single object but split to both an instance of Scientist and the underlying Person object. If for some reason we compare a reference to the Scientist part with a reference to the Person part without further measures, this comparison would yield false which is probably not what we expect. Here is an example:
public boolean find(Person lookFor, Vector personList) {
for (Iterator i = personList.iterator(); i.hasNext(); ) {
if ((Person)i.next() == lookFor) {
return true;
}
}
return false;
}
...
Vector allPersons = new Vector();
Person hawking = new Person("Stephen Hawking");
allPersons.addElement(hawking);
Scientist profHawking = new Scientist(hawking);
if (find(profHawking, allPersons)) { ... } // false
A solution for this example is to let the role object simply obtain the comparand of the associated person:
profHawking.comparand = hawking.comparand;
Afterwards, the expression profHawking == hawking evaluates to true and therefore the above call of find returns the correct result.
(Another solution in Java would be to override the equals(...) method in the Scientist class so that it compares the underlying Person object, and use it accordingly. However, this is not as simple as it seems at first sight. For example, a programmer has to ensure that equals(..) is a reflexive, symmetric, transitive and consistent operation; furthermore, he/she is generally required to also override hashCode() accordingly (see the JDK API documentation for further information). A comparand assignment implicitly and simultaneously fulfils all these requirements, without further effort on the programmer's part.)
As Scientist is a subclass of Person a scientist may be introduced in whatever context an ordinary person is expected. Thus, when "decorating" a person with a Scientist role at runtime, we might also want to replace any occurence of the original Person instance by the more meaningful Scientist instance. However, in pure Java this is a problem as references to an object may be scattered all over the running program. It is hardly possible to re-adjust all these references, at least if something like that had not been anticipated and properly been prepared for.
Instead, we apply the #= operator, with which dynamic object replacement becomes a convenient task. Essentially, the only thing that needs to be done is to assign the reference to the original person object the referent of the scientist instance:
hawking #= profHawking;All variables that contain the reference to the original person will point to the scientist object afterwards. However, this also includes the person attribute of class Scientist that is used for forwarding methods to the underlying decorated object. This would lead to a forwarding cycle (see figure 2a) and therefore has to be prevented. Fortunately, we can create a second reference to a Person before we decorate it and pass this second reference to the constructor of the decorator:
Person temp #= hawking; // step 1 Person profHawking = new Scientist(temp); // step 2 profHawking.comparand = hawking.comparand; // step 3 (make equality behave well, see above) hawking #= profHawking; // step 4
Figure 2b illustrates how this technique ensures that the forwarding link behaves well.

The use of comparands (1.1) and referent assignment (1.2) in this decorator example may well be combined. This combination actually forms the basis for a generic implementation technique for unanticipated software evolution in Gilgul which is further discussed in the 3rd example (see below).
The State Pattern [GHJV95] is useful when you have an object that can be in one of several states, with somewhat different behavior in each state, for example a TCP connection. An implementation of this pattern in Java usually looks like shown in figure 3. The context class (TCPConnection) maintains a state object that represents the current state of the TCP connection. The actual state-dependent behavior is encapsulated into an extra state class (subclass of TCPState).

Though often used, there are still some impurities with this implementation technique:
Luckily, Gilgul's new operations are means to overcome these issues.

Figure 4 presents an implementation of the State Pattern/TCP connection example in Gilgul. Here, the context class is merged with the state class to form a single abstract class TCPConnection whose name can be used in type declarations. A TCP connection is actually represented by an instance of one of the subclasses TCPEstablished, TCPListen or TCPClosed. Consequently, there are no split objects, no need for forwarding methods and state may be determined by just checking for class membership. The interesting question is how state changes are implemented in this scenario.
If the state of the connection changes, the #= operator is used to replace the object that represents this connection by an instance of the proper other subclass. For example, this may be done in a changeState() method in TCPConnection:
abstract class TCPConnection {
void changeState(TCPConnection newState) {
/* perform necessary initializations */
this #= newState; // definite last assignment.
}
...
}
Usage:
TCPConnection connection = new TCPListen(...); ... connection.changeState(new TCPEstablished(...));
To preserve compliance with the typing rules for the #= operator, the subclasses of TCPConnection are declared typeless. Consequently, instances of these classes (TCPEstablished, etc.) can always be replaced by instances of any class in the given hierarchy.
The suggested implementation technique for the State Pattern may be regarded exemplary for other scenarios where an explicit indirection should become implicit as well as it may inspire improved implementation techniques for other Design Patterns like Proxy, Memento, and so on.
The initial decorator example suffered from a certain degree of artificiality. In general, however, the use of decorators in combination with Gilgul's features turns out to be particularly valuable in terms of unanticipated software evolution.
Unanticipated changes of software requirements occur repeatedly in practice, and they cannot be prepared for by definition. The arising changes to software can usually be made effective only by stopping an old version of a program and starting the new one. One apparent drawback of that approach are downtimes that may induce a high cost. A technique to perform subsequent unanticipated adaptations of an already active program is therefore highly desirable.
We give a small Client/Server application as an example to demonstrate how Gilgul is utilized to facilitate these unanticipated adaptations at runtime. The complete source code and (Gilgul) class files of that example can be downloaded.
Suppose we have a Stock Ticker Service running on a server that maintains values of a certain stock and periodically updates these values. At any time, arbitrary clients may request the current stock value. The following is a rudimentary implementation of such a server application that uses sockets for communication and maps each client request to a new server side thread that handles communication. Note that StockTicker implements the Singleton Pattern [GHJV95].
StockTicker (main class, updates stock value regularly):
import java.util.*;
class StockTicker implements Runnable {
// a singleton
static private StockTicker instance = new StockTicker();
static public StockTicker getInstance() { return instance; }
protected double currentStockValue;
protected final String stockCurrency;
protected StockTicker() {
currentStockValue = 5000.00;
stockCurrency = "DEM";
}
public synchronized double getCurrentStockValue() {
return currentStockValue;
}
protected synchronized void setCurrentStockValue(double d) {
currentStockValue = d;
}
protected String printStockValue() { /* print value and currency */
return ((int)getCurrentStockValue()) + " " + getStockCurrency();
}
protected synchronized void refreshCurrentStockValue() {
setCurrentStockValue(getCurrentStockValue()+Math.random()*50 - 25);
}
public synchronized String getStockCurrency() {
return stockCurrency;
}
public void run() {
try {
for (;;) {
Thread.sleep(2000);
refreshCurrentStockValue();
}
} catch (InterruptedException e) { }
}
public static void main(String args[]) {
new Thread(CommandLineClassLoader.getClassLoader()).start(); // start background thread
new Thread(getInstance()).start(); // start stock ticker
new Thread(new StockTickerClientListener()).start(); // start client listener
}
}
StockTickerClientListener (dispatches client requests):
import java.io.*;
import java.net.*;
class StockTickerClientListener implements Runnable {
public void run() {
try {
ServerSocket server = new ServerSocket(6323);
for (;;) {
Socket socket = server.accept();
new Thread(new StockTickerClientThread(socket)).start();
}
} catch (IOException e) {}
}
}
StockTickerClientThread (responds to client request):
import java.io.*;
import java.net.*;
class StockTickerClientThread implements Runnable {
protected Socket socket;
public StockTickerClientThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
PrintWriter out = new PrintWriter( new OutputStreamWriter( socket.getOutputStream()));
out.println(StockTicker.getInstance().printStockValue());
out.close();
} catch (IOException e) { System.out.println(e); }
}
}
The StockTicker maintains a currency for the stock values, in the above case good old DEM. Let us take a few steps back in time, when DEM (and other European currencies) have been replaced by Euro and the need to convert DEM amounts to Euro has occured by dividing them by 1.95583. Of course, now our stock ticker should use the Euro currency as well. At first, it is not difficult to gain a EuroStockTicker. We can simply apply the Decorator pattern again and add the needed conversion functionality.
EuroStockTicker (decorates StockTicker):
import java.util.*;
class EuroStockTicker extends StockTicker {
private StockTicker origTicker;
public String getCurrency() { return "Euro"; }
public synchronized double getCurrentStockValue() {
return origTicker.getCurrentStockValue()/1.95583;
}
protected synchronized void setCurrentStockValue(double d) {
origTicker.setCurrentStockValue(d*1.95583);
}
protected synchronized void refreshCurrentStockValue() { /* make it more speculative */
setCurrentStockValue(getCurrentStockValue()+Math.random()*80 - 40);
}
}
The real challenge is the actual replacement of StockTicker by EuroStockTicker. There are some important issues to be taken into account:
CommandLineClassLoader (background thread)
import java.io.*;
public class CommandLineClassLoader implements Runnable {
static private CommandLineClassLoader loader = new CommandLineClassLoader();
static public CommandLineClassLoader getClassLoader() { return loader; }
private BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
protected BufferedReader getInput() { return input; }
protected void processCommand(String classToLoad) {
try {
Class.forName(classToLoad);
} catch (ClassNotFoundException e) {
System.out.println("Class not found!");
}
}
public void run() {
try {
for (;;) {
Thread.sleep(20);
System.out.print("> ");
try {
processCommand(getInput().readLine().trim());
} catch (IOException e) {
System.out.println("IO exception.");
}
}
} catch (InterruptedException e) { }
}
}
The replacement is performed in a static initializer and the constructor of class EuroStockTicker (see below). Thus, for initiation of the evolution step it is sufficient to load the class EuroStockTicker into the running application, for example by means of processCommand() of the CommandLineClassLoader.
class EuroStockTicker {
/* first part: see above */
...
protected EuroStockTicker(StockTicker st) {
StockTicker old #= st; // step 1
this.origTicker = old; // step 2
this.comparand = st.comparand; // step 3
}
static {
StockTicker st = StockTicker.getInstance(); // get the singleton
st #= new EuroStockTicker(st) with global recall; // step 4
}
}
Here again we employ the four steps that are already presented in example 1:
What actually happens with regard to the recall in the 4th step is that the StockTicker thread is requested to perform a recall by the background thread (CommandLineClassLoader) when this one executes the static initializer during initialization of the new class. In consequence of calling Thread.sleep() in the endless loop, the StockTicker thread calls checkRecall() and therefore recognizes this request. Therefore an instance of java.lang.Recall is thrown that is finally caught by the implicit recall handler of the run() method (which is actually a native exception handler as the run() method is not called from within bytecode). At this moment, the referent assignment is carried out in the CommandLineClassLoader thread. Only when this operation has finished the run() method in the stock ticker thread is called again, now with the new EuroStockTicker instance as its receiver.
The decorator scenario demonstrated above can be applied as a generic implementation technique to perform adaptations to running applications. Thus, dynamic software evolution becomes possible in an entirely unanticipated way.
|
|
|
|
|
|
|
|
|
|
|