I was faced recently with the same problem as yours.
I needed to:
- process a collection of subclasses
using a common interface avoiding
overloading
- the action code should be in the server-side
classes not in the client-side value object subclasses
- I wanted to get rid of the "if/instanceof" structure
The solution turned out to be rather cumbersome (but it works!):
CLIENT-SIDE VALUE OBJECTS (command objects):
These classes follow the Command pattern but delegating
the action to a server-side object.
public abstract class Order
{
....
abstract void processOrder(OrderProcessor op);
}
public final class AddOrder extends Order
{
....
void processOrder(OrderProcessor op)
{
op.processOrder(this);
}
}
public final class MoveOrder extends Order
{
....
void processOrder(OrderProcessor op)
{
op.processOrder(this);
}
}
SERVER SIDE CLASSES:
The server side classes consiste of a package-scope interface
that declares de OrderProcessor interface.
The OrderProcessor implementation is a private object
inside the OrderBroker (maybe an EJB).
The OrderBroker presents an "immutable" interface out to the client.
(package-scope)
interface OrderProcessor
{
void processOrder(AddOrder o);
void processOrder(MoveOrder o);
}
// server-side command broker
public final class OrderBroker
{
// command executor
private OrderProcessor op = new OrderProcessor()
{
public void processOrder(AddOrder o)
{
System.out.println("Processing "+o);
}
public void processOrder(MoveOrder o)
{
System.out.println("Processing "+o);
}
};
// IMMUTABLE INTERFACE METHOD
public void processOrders(Order[] o)
{
for (int i = 0; i < o.length; i++)
{
o[i].processOrder(op);
}
}
}
CLIENT CODE:
public class Client
{
public static void main(String[] args)
{
Order o1 = new AddOrder();
Order o2 = new MoveOrder();
OrderBroker broker = new OrderBroker();
broker.processOrders(new Order[]{o1, o2});
}
}
-
Server-Side Command or True RTTI Killer (10 messages)
- Posted by: Agusti Sanchez
- Posted on: December 27 2002 17:12 EST
Threaded Messages (10)
- Server-Side Command or True RTTI Killer by Gal Binyamini on December 27 2002 20:29 EST
- Server-Side Command or True RTTI Killer by Giedrius Trumpickas on January 03 2003 14:04 EST
- Server-Side Command or True RTTI Killer by Race Condition on January 03 2003 18:24 EST
- Server-Side Command or True RTTI Killer by Cristian Negoita on January 09 2003 07:06 EST
- Server-Side Command or True RTTI Killer by Abhay Shukla on January 09 2003 00:50 EST
-
Server-Side Command or True RTTI Killer by Joe Cheng on January 09 2003 09:15 EST
- Server-Side Command or True RTTI Killer by Race Condition on January 09 2003 10:27 EST
-
Server-Side Command or True RTTI Killer by Joe Cheng on January 09 2003 09:15 EST
- Server-Side Command or True RTTI Killer by Race Condition on January 03 2003 18:24 EST
- clear it out by Dieter Cailliau on February 21 2003 20:44 EST
- clear it out - the complete post by Dieter Cailliau on February 21 2003 20:54 EST
- clear it out - the complete post by Gal Binyamini on February 22 2003 10:37 EST
-
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Gal Binyamini
- Posted on: December 27 2002 20:29 EST
- in response to Agusti Sanchez
Hi Agusti. Thanks for sharing the pattern.
I have several problems with this pattern:
- It's not a command pattern. The functionality is deferred to a different class (OrderProcessor). All the processOrder method of the "command" does is invoke this method. You might as well use the OrderProcessor directly and avoid this meaningless indirection. Besides, if you implement all these methods the same way, why not put them in the abstract superclass?
- repeated method declaration in the OrderProcessor interface. This gets around RTTI, but completely the wrong way IMHO. You might as well just use different names for each method. The whole idea in having a common base class is that you only declare the method once for that base class, and then when other orders are added to the system you don't need to change your order processor.
I think a better solution would be to load some sort of "command handler" for each command, which would enable polymorphic handling of commands. The mapping between commands and handlers can be static (e.g., handler class name can be fetched from command) or dynamic (e.g., a hashtable is populated with the mapping at start-up).
Gal -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Giedrius Trumpickas
- Posted on: January 03 2003 14:04 EST
- in response to Agusti Sanchez
It's just visitor pattern from GOF. Nodes are diiferent types of orders and visitor - OrderProcessor.
So why it's new pattern. Next time before posting RTFM. -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Race Condition
- Posted on: January 03 2003 18:24 EST
- in response to Giedrius Trumpickas
Hey Giedrius you DMF, you should RTFM. -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Cristian Negoita
- Posted on: January 09 2003 07:06 EST
- in response to Race Condition
What is DMF? -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Abhay Shukla
- Posted on: January 09 2003 00:50 EST
- in response to Giedrius Trumpickas
Whats this RTFM? -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Joe Cheng
- Posted on: January 09 2003 09:15 EST
- in response to Abhay Shukla
RTFM = Read the Friggin' Manual
DMF ?= Dumb Mother Fudgecake
Edited for a family audience :) -
Server-Side Command or True RTTI Killer[ Go to top ]
- Posted by: Race Condition
- Posted on: January 09 2003 10:27 EST
- in response to Joe Cheng
You've got it Joe! -
clear it out[ Go to top ]
- Posted by: Dieter Cailliau
- Posted on: February 21 2003 20:44 EST
- in response to Agusti Sanchez
The idea is fine but the implementation is not.
This is probably what remains after refactoring some time:
add a method perform(Command) to your ejb; the implementation calls back to the command, passing itself as a kind of execution context:
BeanImpl
{
}
Command is abstract and has one method perform(Object target) -
clear it out - the complete post[ Go to top ]
- Posted by: Dieter Cailliau
- Posted on: February 21 2003 20:54 EST
- in response to Agusti Sanchez
Add a method perform(Command) to your ejb; the implementation calls back to the command, passing itself as a kind of execution context:
BeanImpl
{
perform(Command command)
{
command.perform(this);
}
}
Command is abstract and has one method perform(Object execution_context). The subclasses "know" which context to expect, so they cast it, and use it to perform their work, remotely:
AddUsers extends Command
{
private Group group;
private Collection users;
perform(Object execution_context)
{
BeanItf ejb = (BeanItf)execution_context;
Iterator user = users.iterator();
while (users.hasNext())
{
Object user = users.next();
ejb.addUser(user);
ejb.assignUserToGroup(user,group);
}
}
}
The cool thing is: the code in the command is programmed using the same "api" as the client, but it skips many remote calls, by executing the command logic inside the ejb. This also means it's a single transaction (if perform is transaction required).
I think this technique is very important to be able to stablize your ejb interfaces: define a basic set of "simple" methods, and program more complex operations as Commands. -
clear it out - the complete post[ Go to top ]
- Posted by: Gal Binyamini
- Posted on: February 22 2003 10:37 EST
- in response to Dieter Cailliau
This is indeed a nice use of the Command pattern. It is widely documented and Dieter explained the main advantages well. I want to point out some of the disadvantages:
- Client code has to be deployed in server unless you want to rely on remote class loading, which I think is a bad idea: It poses a security risk (dispite of Java's secure class loading); It fills the server's VM up with client specific classes; And there is no simple way to avoid conflicts between different client's .class files (except loading each with a different class loader, which is highly expensive).
- If the client code deployed on the server uses server-internal classes (like local interfaces of entities, utility classes available on the server, etc) they have to also be available on the client's VM, which causes undesirable dependencies. But using server-internal classes are one of the advantages of this pattern.
These problems cause a strong two-way coupling between the client and the server. If this has a potential of causing problems in your project, you can use a slightly different approach: seperate commands into "command descriptor" client objects and "command handler" server objects. The handler can be looked up by the type of descriptor (for instance, from a static hashmap loaded on init time). The command descriptor is a pure client class, with no references to server-level objects. The handler is a server object. The client doesn't need to know it, and the server can change it's implementation without affecting the client. This solution reduces the dependency to one-way (server depends on client's command descriptor, which means server knows clients usecases at some level), which is neccesary with this pattern anyway unless you allow remote class loading.
Gal