Code for building generic chains of validations and transformations to an object
I am trying to write some general code to do the following. Given two kinds of "operations", (a) validation (eg. input: object & context -> output: boolean), and (b) transformation (eg. input: object_A, context -> output: object_B) -objects of any type-.
I want to be able to build chains of "operations", in which an input object and its context can be submitted through (eg. to validate and transform the object). Returning immediately if the object is "invalid" and being able to get the transformed object if it finished "valid".
Idea is that "validations" and "transformations" can be "plugable" functions that other people write and assemble in a chain (eg. they build chains and submit objects through them).
I managed to do the following code, which compiles and seems to work. However, I'm not an expert on generics and would like to hear feedback about possible pitfalls, enhancements, or even maybe some other better/easier approach to the problem. Thanks in advance.
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
interface Operation<T, U, V> {
U execute(T a, V context);
}
abstract class Validation<T, V> implements Operation<T, Boolean, V> {
@Override
public Boolean execute(T a, V context) {
return executeValidation(a, context);
}
public abstract Boolean executeValidation(T a, V context);
}
abstract class Transformation<T, U, V> implements Operation<T, U, V> {
@Override
public U execute(T a, V context) {
return executeTransformation(a, context);
}
public abstract U executeTransformation(T a, V context);
}
class OperationsChain {
List<Operation<Object, Object, Object>> operations = new ArrayList<Operation<Object, Object, Object>>();
Object currentObj;
public <T, V> Boolean run(T a, V context) {
Boolean valid = false;
currentObj = a;
for (Operation<Object, Object, Object> operation : operations) {
if (operation instanceof Validation) {
valid = (Boolean) operation.execute(currentObj, context);
} else if (operation instanceof Transformation) {
currentObj = operation.execute(currentObj, context);
}
if (!valid) {
break;
}
}
return valid;
}
@SuppressWarnings("unchecked")
public <T, U, V> void addOperation(Operation<T, U, V> operation) {
operations.add((Operation<Object, Object, Object>) operation);
}
public Object getCurrentObject() {
return currentObj;
}
}
class ValidationOne extends Validation<MapObject, Map<String, Object>> {
public Boolean executeValidation(MapObject a, Map<String, Object> context) {
if (context.containsKey("validation 1")) {
return (Boolean) context.get("validation 1");
} else {
return false;
}
}
}
class ValidationTwo extends V开发者_JS百科alidation<MapObject, Map<String, Object>> {
public Boolean executeValidation(MapObject a, Map<String, Object> context) {
if (context.containsKey("validation 2")) {
return (Boolean) context.get("validation 2");
} else {
return false;
}
}
}
class TransformationOne extends Transformation<MapObject, MapObject, Map<String, Object>> {
public MapObject executeTransformation(MapObject a, Map<String, Object> context) {
if (context.containsKey("transformation 1")) {
a.addField("data", (String) context.get("transformation 1"));
}
return a;
}
}
class MapObject {
Map<String, String> fields = new HashMap<String, String>();
public void addField(String key, String value) {
fields.put(key, value);
}
public String getField(String key, String value) {
if (fields.containsKey(key)) {
return fields.get(key);
} else {
return null;
}
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append(": ");
sb.append(entry.getValue());
sb.append("\n");
}
return sb.toString();
}
}
class OperationsChainDriver {
public static void main(String[] args) {
OperationsChain oc = new OperationsChain();
oc.addOperation(new ValidationOne());
oc.addOperation(new TransformationOne());
oc.addOperation(new ValidationTwo());
oc.addOperation(new TransformationOne());
Map<String, Object> context = new HashMap<String, Object>();
context.put("validation 1", true);
context.put("validation 2", false);
context.put("transformation 1", "aloha");
MapObject mapObject = new MapObject();
mapObject.addField("field 1", "hello");
Boolean result = oc.run(mapObject, context);
if (result == true) {
System.out.println("valid\n"+oc.getCurrentObject().toString());
} else {
System.out.println("invalid\n"+oc.getCurrentObject().toString());
}
}
}
Generics are about type safety - not having to cast, because as you surely know casts are risks proved runtime. You have a very generic design yet get very concrete to and the like and have to cast a lot - this shouldn't happen since it defeats the reason to use generics at all.
As as side note: why not give an operation a method isValid that has always a return type of Boolean, a transformation can fail, too, so yo don't have to make a difference between validation and transformation. Or let it put a value in a context - the operation could know its context and could use it without casts. An operation chain could know its context and could get the results without casts.
Anyway - as long as you code has casts you are still not finished with it.
This kind of task is one that I think a functional language would be ideal for, e.g. Scala (which runs on the JVM and is perfect for interoperating with Java code), or Haskell (which doesn't run on the JVM, but has some other advantages).
OK, I understand if you don't want to learn a new programming language. But one of the key advantages would be that your code should be shorter and easier to read and reason about.
精彩评论