Код для построения общих цепочек проверок и преобразований в объект - PullRequest
2 голосов
/ 24 апреля 2011

Я пытаюсь написать общий код, чтобы сделать следующее. Даны два вида «операций», (а) проверка (например, input: object & context -> output: boolean) и (b) преобразование (например, input: object_A, context -> output: object_B) - объекты любого тип-.

Я хочу иметь возможность создавать цепочки «операций», в которых входной объект и его контекст могут быть переданы (например, для проверки и преобразования объекта). Возврат немедленно, если объект «недействителен» и возможность получить преобразованный объект, если он закончил «действителен».

Идея состоит в том, что «проверки» и «преобразования» могут быть «подключаемыми» функциями, которые другие люди пишут и собирают в цепочку (например, они строят цепочки и передают объекты через них).

Мне удалось сделать следующий код, который компилируется и, кажется, работает. Тем не менее, я не эксперт по дженерикам и хотел бы услышать отзывы о возможных подводных камнях, улучшениях или даже, возможно, о каком-либо другом более / более простом подходе к проблеме. Заранее спасибо.

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 Validation<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());
        }
    }
}

Ответы [ 2 ]

1 голос
/ 24 апреля 2011

Обобщения касаются безопасности типов - не обязательно использовать приведение, потому что, как вы наверняка знаете, приведение типов является доказанным риском во время выполнения.У вас очень общий дизайн, но вы очень конкретны и тому подобное, и вам приходится много разыгрывать - этого не должно произойти, потому что это побеждает причину вообще использовать дженерики.Если у операции есть метод isValid, который всегда имеет возвращаемый тип Boolean, преобразование также может завершиться ошибкой, поэтому вам не нужно делать различий между проверкой и преобразованием.Или пусть он помещает значение в контекст - операция может знать его контекст и может использовать его без приведения.Цепочка операций может знать свой контекст и может получать результаты без приведения.

В любом случае - до тех пор, пока в вашем коде есть приведения, вы еще не закончили с ним.

0 голосов
/ 24 апреля 2011

Задача такого рода, на мой взгляд, была бы идеальной для функционального языка, например. Scala (который работает на JVM и идеально подходит для взаимодействия с Java-кодом) или Haskell (который не работает на JVM, но имеет некоторые другие преимущества).

Хорошо, я понимаю, если вы не хотите изучать новый язык программирования. Но одно из ключевых преимуществ заключается в том, что ваш код должен быть короче и проще для чтения и рассуждений.

...