Wednesday, August 29, 2007

Object Oriented Design (OOD) Patterns: The Retriever Pattern

I find myself using this pattern quite a bit in my Java coding for CoCreate and I decided to name it the “Retriever Pattern”. Now it might have another name but, I have not seen it published anywhere. The retriever pattern is an optimistic getter. Here is how it works.

Algorithm:

- Given a reference to a data source.

- Get the data and cache it locally.

- When asked for the data, check to see if the data can be retrieved again.

- If not return the local cached value.

Uses:

- The object providing the data is out of scope.

- The database result has gone out of scope.

- The object providing the data is synchronized and you may deadlock.

Cautions:

- A retriever is not as reliable as a listener. If a data value has been updated, the update will not be seen until the data next retrieval.

- A retriever will not update once the source data object goes out of scope. From that point forward it is static.

Advantages:

Using a retriever there is no need to worry about objects going out of scope in the middle of a presentation of the data. I have found this very useful in web services when creating a soap response from database data. I can reliably close out the database connection, and perform other business logic, prior to assembling the soap response object. The soap response object can be assembled as if the original database object was still in scope.

The same business objects can then be used in a fat client where the underlying data objects will not go out of scope. This simplifies coding and encourages reuse of the same business logic in the fat client as well as the web services.

Sample: A simple Java Retriever class that uses a week reference. I use these a lot to keep our systems at CoCreate from having garbage collection issues. All the JavaDoc and sample files can be found at http://www.davegraham.org/samples/javadoc/





package org.davegraham.retriever;

/**
* Used in a Sample of the Retriever pattern. Consider this your persistent storage class.
* See PersistentDataStorage.java source file.
* @author DaveGraham.org
* @version 1.0
* @since JDK 1.5
*/
public class PersistentDataStorage {
private static String mockDatabaseStore;

/**
* Used to read the value from the persistent data storage area.
* @return The current value stored in the storage area
*/
public static String read() {
return mockDatabaseStore;
}

/**
* Used to write data into the persistent data storage area.
* @param value The value to store in the storage area
*/
public static void write(final String value) {
mockDatabaseStore = value;
}
}


package org.davegraham.retriever;

/**
* A sample business object bean for understanding the OOD Retriever Pattern.
* See BusinessObject.java source file.
* @author DaveGraham.org
* @version 1.0
* @since JDK 1.5
*/
public class BusinessObject {

/**
* Constructor and initializer for the sample business object.
* @param value The value to initialize the business object
*/
public BusinessObject(final String value) {
this.setValue(value);
}

/**
* A getter to obtain the value as used by the business object.
* @return a string value from the business object
*/
public String getValue() {
return PersistentDataStorage.read();
}

/**
* A setter to set a new value into the business object
* @param value the new String value for the busines object
*/
public void setValue(final String value) {
PersistentDataStorage.write(value);
}
}


package org.davegraham.retriever;

import java.lang.ref.WeakReference;

/**
* A sample class that implements the OOD Retriever Pattern for
* a weakly referenced object.
* This retriever pattern object is constructed around a BusinessObject
* in such a way that the BusinessObject may go out of scope
* and the retriever will continue to return "reasonable"
* results about the value within the BusinessObject.
* See Retriever.java source file.
* @author dgraham
* @version 1.0
* @since JDK 1.5
*/
public class Retriever {

private final WeakReference weakReference;
private String cachedValue;

/**
* Constructs a retriever pattern object around a BusinessObject.
* @param businessObject The busines object from which to retrieve data
*/
public Retriever(final BusinessObject businessObject) {
weakReference = new WeakReference(businessObject);
cachedValue = businessObject.getValue();
}

/**
* Returns either the current value or the last known value
* that was contained in the BusinessObject. This is the
* retriever pattern at work.
* @return the retrieved value
*/
public String getValue() {
BusinessObject strongReference = weakReference.get();
String result;
if ( strongReference == null ) {
result = cachedValue;
} else {
result = strongReference.getValue();
cachedValue = result;
}
return result;
}

/**
* Gets the WeakReference to the BusinessObject.
* Normally this would not be exposed in a retriever pattern.
* It is exposed here to demonstrate in the RetrieverTest unit
* tests that the garbage collector has indeed flushed away
* the BusinessObject reference.
* @return the WeakReference to the BusinesObject
*/
public WeakReference getWeakReference() {
return weakReference;
}
}


package org.davegraham.retriever;

import junit.framework.*;
import java.lang.ref.WeakReference;
import org.davegraham.retriever.*;

/**
* These tests demonstrate the advantages and disadvantages of the retriever pattern.
* They exist only as a sample for people to understand the retriever pattern.
* See RetrieverTest.java source file.
* @author DaveGraham.org
* @version 1.0
* @since JDK 1.5
*/
public class RetrieverTest extends TestCase {

public RetrieverTest(String testName) {
super(testName);
}

public static Test suite() {
return new TestSuite(RetrieverTest.class);
}

public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
System.exit(0);
}
/**
* This test demonstrates the basic operation of the retriever pattern.
* It shows that even after the business object has gone out of scope
* the retriever keeps working and returning the last known good value.
*/
public void testBasicOperationOfRetriverPattern() {
BusinessObject businessObject = new BusinessObject("apple");
Retriever retriever = new Retriever(businessObject);
assertEquals( "Initial value incorrect", "apple", retriever.getValue());
businessObject.setValue("orange");
assertEquals( "Current value incorrect", "orange", retriever.getValue());
businessObject.setValue("bannana");
assertEquals( "Current value incorrect", "bannana", retriever.getValue());
businessObject = null;
System.gc();
assertNull( "WeakRefrence should have been cleaned up by garbage collector", retriever.getWeakReference().get());
assertEquals( "Last known value incorrect", "bannana", retriever.getValue());
assertEquals( "Last known value incorrect", "bannana", retriever.getValue());
}

/**
* This test demonstrates where the retriever pattern fails.
* A retriever is not as strong as a listener so it does not get
* notified of new values. It can only return the last known good value.
* This demonstrates that retrievers are best used in conditions where
* its life time beyond the existence of its source (BusinessObject) is
* not expected to be long.
*/
public void testTheFailureOfTheRetrieverPattern() {
BusinessObject businessObject = new BusinessObject("apple");
Retriever retriever = new Retriever(businessObject);
assertEquals("Initial value incorrect", "apple", PersistentDataStorage.read());
assertEquals("Initial value incorrect", "apple", retriever.getValue());
businessObject.setValue("orange");
assertEquals("Initial value incorrect", "orange", PersistentDataStorage.read());
assertEquals("Initial value incorrect", "orange", retriever.getValue());
businessObject.setValue("bannana");
assertEquals("Current value incorrect", "bannana", PersistentDataStorage.read());
assertEquals("Current value incorrect", "bannana", retriever.getValue());
businessObject.setValue("grapes");
businessObject = null;
System.gc();
assertNull("WeakRefrence should have been cleaned up by garbage collector", retriever.getWeakReference().get());

// This is the important part! Examine these two lines!
assertEquals("The PersistentDataSorage is the last value set.", "grapes", PersistentDataStorage.read());
assertEquals("Last known value is not the value in the PersistentDataSorage", "bannana", retriever.getValue());
// A listener pattern can solve this problem. But listeners create there own problems particularly
// when it comes to garbage collection. The retriever pattern is very friendly to out-of-scope problems.
}
}

No comments: