A recent design discussion resulted in a solution that has elements with strong similarities to the "Execute Around Method" and "Proxy" patterns while, in both cases moving outside the scope of the usual descriptions of these patterns [ Henney2001 , GOF1995 ].
This article presents the scenario we encountered as a pattern story. It notes the similarities and differences to the canonical forms of "Façade", "Execute Around Method" and "Proxy" patterns; and, raises the question "are these still the same patterns - or have they been cooked beyond recognition?"
Intent
To grant access to specific functionality only between paired operations.
Motivation
When updating large amounts of state it may be desirable to ensure that "before" and "after" messages are sent to the owner of that state so that any necessary preparation or cleanup may be applied - or that concurrent operations can be inhibited.
Consider a batch update of the product lines available within a system. A complete list of product lines is supplied by an external source and used to create, amend or delete product lines available within the system. Because the only indication that a product is to be deleted is that no corresponding product line is supplied it is necessary to accumulate information regarding the product lines accessed during the update and to deal with deletions when the update completes. (One of the delights of interworking with client's "legacy systems" is that, in this instance, they cannot provide perfectly simple information - such as positive notification of deletes.)
Typically the product lines are stored as rows within an RDBMS and accessed via a Broker class that provides the persistence mechanism. The Broker provides a finer grained and more extensive interface than is required by the needs of the application and access is mediated by a Director that acts as a Façade [GOF1995]. (In an EJB based system the Director would be a stateless session bean and a Broker used in place of an entity bean to avoid the cost of unwanted synchronisation.)
Because Java allows classes to grant privileged access to package members it is possible (and not uncommon) to restrict access to some Broker methods to the package and place both Broker and Director in the same package. This adaptation of the pattern enforces use of the Façade by classes outside the package and allows protocols to be enforced. In particular declaring "start", "update" and "finish" methods with package access will limit the code that calls them to the this package.
In keeping with its role as a Façade the Director should have the responsibility of calling "start" and "finish" methods on the Broker before and after processing of the batch update. However, receipt of the update is not the responsibility of the Director and, in fact, is driven by another part of the system entirely. (It might be possible to wrap the processing of the input as an "iterator" passed to the Director - however this would add complexity to the system.)
This leads us towards a variation of "Execute Around Method" where the paired operations are "start" and "finish" method calls (not resource allocation) and whose target is the Broker (not self invocation on the Resource object as described by Kevlin Henney [Henney2001]). We still have Kevlin's Command object - but not only has Resource been split into Director and Broker it is not passed to the Command's applyTo method ("run" in Kevlin's paper).
However, we've not yet resolved the full context - as the implementation of the Command interface still won't have access to the necessary Broker methods. But this is where our variation on Proxy comes into play: we define a BrokerProxy class that, being in the same package as the Broker, has access to the necessary functionality and can implement public forwarding functions. It is this (not a resource) that is passed to the Command's applyTo method by the Director. (To ensure that the Director isn't bypassed construction of the BrokerProxy is given package access.)
This usage of Proxy differs from that described in [GOF1995] because additional functionality is exposed by the Proxy class. Specifically it doesn't substitute for a Broker. (I feel it is far closer to Proxy than to Adapter or Bridge.)
Participants
-
Broker (Broker) - supplier of the functionality to which access is controlled
-
Façade (Director) - responsible for enforcing the access control protocol
-
Command (Command) - declares a usage (applyTo) method for the functionality
-
ConcreteCommand (UpdateCommand) - user of the access controlled functionality
-
Proxy (BrokerProxy) - forwards calls to the Broker within the controlled scope
Consequences
The client code in UpdateCommand.applyTo() has access to the Broker.update() method via the BrokerProxy . The Director is able to ensure that start() and finish() are invoked at the appropriate points in the executeAround method.
Sample code
The following code illustrates the implementation of this dish in Java. We'll assume that a product line comprises its name and price:
public class Product { private String name; private int price; public Product(String name, int price){ this.name = name; this.price = price; } public String getName() { return name; } public int getPrice() { return price; } }
A Broker class called ProductBroker manages the persistent storage for products. It provides methods for retrieving Product information, and for updating the product table:
class ProductBroker { // ... List listProductNames() { // ... } Product getProduct(String name) { // ... } void startUpdate() { // ... } void update(Product data) { // ... } void finishUpdate() { // ... } }
Because the broker doesn't provide public access we provide public access to the update method via the Proxy class:
public class ProductBrokerProxy { // ... public void update(Product data) { broker.update(data); } }
The Command interface by which the client code supplies the update logic is:
public interface ProductUpdateCommand { public void applyTo( ProductBrokerProxy target); }
All of this is co-ordinated by the ProductDirector façade as follows:
public class ProductDirector { // ... public void updateProducts( ProductUpdateCommand command) { final ProductBrokerProxy proxy = new ProductBrokerProxy(); synchronized (this) { broker.startUpdate(); command.applyTo(proxy); broker.finishUpdate(); } } }
While there appear to be a lot of pieces to the implementation of the final design the client code is clear and does not rely on following a "start/update/finish" protocol for correctness.
public class UpdateProducts { // ... public static void main(String[] args) { // Set up some example data... final Vector data = new Vector(); data.add(new Product("beans", 27)); data.add(new Product("chicken", 525)); // Process the example data... final Iterator iter = data.iterator(); director.updateProducts( new ProductUpdateCommand() { public void applyTo( ProductBrokerProxy target) { while(iter.hasNext()) { target.update( (Product)iter.next()); } } }); } }
Acknowledgements
Thanks to Jason Martin and Andrew Rigley who: brought the motivating example to my attention; allowed me to participate in an interesting design session that led to the above resolution; and, reviewed the draft article. Additional thanks are also due to Jason who supplied the sample code on which the above fragments are based.
Alan Griffiths < alan.griffiths@microlise.com >
Jason Martin < jason.martin@microlise.com >
Andrew Rigley < andy.rigley@microlise.com >
References
[Henney2001] Kevlin Henney, "Another Tale of Two Patterns" Java Report March 2001 http://www.two-sdg.demon.co.uk/curbralan/papers/AnotherTaleOfTwoPatterns.pdf
[GOF1995] Gamma, Helm, Johnson, Vlissides, Design Patterns ISBN 0-201-63361-2