2011-12-30 14 views
5

He estado luchando con el siguiente problema. Tengo una serie de objetos de función, cada uno con sus propios tipos de entrada y salida definidos a través de argumentos de tipo genérico en java. Me gustaría organizar estos en una cadena para que los datos brutos se introduzcan en la primera función, transformados en el tipo de salida, que es el tipo de entrada del siguiente objeto, y así sucesivamente. por supuesto, esto sería trivial para el código duro, pero me gustaría que el código se pueda conectar a nuevos objetos de función. si acabo de dejar de lado argumentos de tipo (sólo el tipo de salida final), se trata de cómo se ven las cosas:Genéricos de Java: encadenando objeto de función genérico

public T process() { 
     Iterator<Context> it = source.provideData(); 
     for(Pipe pipe : pipeline) { 
      it = pipe.processIterator(it); 
     } 
     return sink.next(it); 
    } 

aquí un iterador sobre los datos se pasan entre los objetos de función, y el contexto debe ser Contexto. ¿Hay alguna manera de mantener el siguiente tipo de tubería enchufable y de mantener la seguridad del tipo?

edición: para mayor claridad, tengo una serie de objetos de función, tuberías. cada uno toma como entrada un tipo particular y saca otro tipo. (en realidad, un iterador sobre estos tipos) estos serán encadenados juntos, por ejemplo, Pipe<A,B> -> Pipe<B,C> -> Pipe<C,D> -> ..., de modo que la salida de una tubería sea el tipo de entrada para la siguiente tubería. También hay una fuente aquí que genera un iterador de tipo A y un receptor que aceptará el tipo (la salida de la tubería pasada). ¿Esto hace las cosas más claras? La pregunta es, porque existe una dependencia crítica en la compatibilidad de los tipos de entrada y salida, ¿hay alguna forma de asegurar esto?

Estoy empezando a pensar que la inserción de la función de objetos en la tubería puede ser el mejor momento para garantizar la seguridad del tipo, pero no estoy seguro de cómo hacerlo.

editar 2: i tener un método sumador para los objetos de función que actualmente se ve como a continuación:

public void addPipe(Pipe<?,?> pipe) { 
    pipeline.add(pipe); 
} 

me gustaría para comprobar si el primer parámetro tipo es el mismo que el "final" de la tubería actual, y lanzar una excepción si no? No creo que haya una buena forma de obtener seguridad en tiempo de compilación aquí. el "extremo" de la tubería de corriente se puede establecer en el segundo parámetro de tipo de la tubería de entrada. No puedo pensar en cómo hacer esto con genéricos, y pasar la información de la clase parece bastante horrible.

+1

La borradura de tipo puede hacer su vida más difícil para funciones totalmente genéricas. El compilador de bytes JVM elimina el tipo de instancia de clases genéricas, por lo que la lista se convierte en la lista . –

+0

¿Puede explicar el requisito un poco más? Incapaz de descubrir lo que tienes y lo que quieres tener. –

+0

, entonces, ¿cuál sería el mejor enfoque para tratar de garantizar la seguridad de tipo al insertar el objeto de función de tubería (cómo?) Y simplemente suprimir las advertencias en este método anterior? – downer

Respuesta

7

Aquí hay una manera de hacerlo. El método de ejecución no es seguro, pero dado que la única forma de agregar una tubería es hacerlo de forma segura, toda la cadena es segura.

public class Chain<S, T> { 
    private List<Pipe<?, ?>> pipes; 

    private Chain() { 
    } 

    public static <K, L> Chain<K, L> start(Pipe<K, L> pipe) { 
     Chain<K, L> chain = new Chain<K, L>(); 
     chain.pipes = Collections.<Pipe<?, ?>>singletonList(pipe);; 
     return chain; 
    } 

    public <V> Chain<S, V> append(Pipe<T, V> pipe) { 
     Chain<S, V> chain = new Chain<S, V>(); 
     chain.pipes = new ArrayList<Pipe<?, ?>>(pipes); 
     chain.pipes.add(pipe); 
     return chain; 
    } 

    @SuppressWarnings({ "rawtypes", "unchecked" }) 
    public T run(S s) { 
     Object source = s; 
     Object target = null; 
     for (Pipe p : pipes) { 
      target = p.transform(source); 
      source = target; 
     } 
     return (T) target; 
    } 

    public static void main(String[] args) { 
     Pipe<String, Integer> pipe1 = new Pipe<String, Integer>() { 
      @Override 
      public Integer transform(String s) { 
       return Integer.valueOf(s); 
      } 
     }; 
     Pipe<Integer, Long> pipe2 = new Pipe<Integer, Long>() { 
      @Override 
      public Long transform(Integer s) { 
       return s.longValue(); 
      } 
     }; 
     Pipe<Long, BigInteger> pipe3 = new Pipe<Long, BigInteger>() { 
      @Override 
      public BigInteger transform(Long s) { 
       return new BigInteger(s.toString()); 
      } 
     }; 
     Chain<String, BigInteger> chain = Chain.start(pipe1).append(pipe2).append(pipe3); 
     BigInteger result = chain.run("12"); 
     System.out.println(result); 
    } 
} 
+0

realmente genial, y como todas las buenas soluciones, es obvio una vez que se sabe :) – downer

+0

Gracias por su respuesta :) Creé un [proyecto github] (https://github.com/smartorigin/Dynamic-Workflow) basado en su respuesta que agrega funcionalidad asíncrona y ejecución paralela. Espero que ayude a alguien. –

0

Aquí hay otra forma de hacerlo: de esta manera permite que un paso de transformación dé como resultado una lista. Por ejemplo, una transformación podría dividir una cadena en múltiples subcadenas. Además, permite el código común de manejo de excepciones si la transformación de cualquiera de los valores produce una excepción. También permite el uso de una lista vacía como valor de retorno en lugar de un valor nulo ambiguo que debe probarse para evitar NullPointerException. El principal problema con este problema es que realiza cada paso de transformación en su totalidad antes de pasar al siguiente paso, que puede no ser eficiente desde el punto de vista de la memoria.

public class Chain<IN, MEDIAL, OUT> { 
    private final Chain<IN, ?, MEDIAL> head; 
    private final Transformer<MEDIAL, OUT> tail; 

    public static <I, O> Chain<I, I, O> makeHead(@Nonnull Transformer<I, O> tail) { 
     return new Chain<>(null, tail); 
    } 

    public static <I, M, O> Chain<I, M, O> append(@Nonnull Chain<I, ?, M> head, @Nonnull Transformer<M, O> tail) { 
     return new Chain<>(head, tail); 
    } 

    private Chain(@Nullable Chain<IN, ?, MEDIAL> head, @Nonnull Transformer<MEDIAL, OUT> tail) { 
     this.head = head; 
     this.tail = tail; 
    } 

    public List<OUT> run(List<IN> input) { 
     List<OUT> allResults = new ArrayList<>(); 

     List<MEDIAL> headResult; 
     if (head == null) { 
      headResult = (List<MEDIAL>) input; 
     } else { 
      headResult = head.run(input); 
     } 

     for (MEDIAL in : headResult) { 
      // try/catch here 
      allResults.addAll(tail.transform(in)); 
     } 

     return allResults; 
    } 

    public static void main(String[] args) { 

     Transformer<String, Integer> pipe1 = new Transformer<String, Integer>() { 
      @Override 
      public List<Integer> transform(String s) { 
       return Collections.singletonList(Integer.valueOf(s) * 3); 
      } 
     }; 
     Transformer<Integer, Long> pipe2 = new Transformer<Integer, Long>() { 
      @Override 
      public List<Long> transform(Integer s) { 
       return Collections.singletonList(s.longValue() * 5); 
      } 
     }; 
     Transformer<Long, BigInteger> pipe3 = new Transformer<Long, BigInteger>() { 
      @Override 
      public List<BigInteger> transform(Long s) { 
       return Collections.singletonList(new BigInteger(String.valueOf(s * 7))); 
      } 
     }; 
     Chain<String, ?, Integer> chain1 = Chain.makeHead(pipe1); 
     Chain<String, Integer, Long> chain2 = Chain.append(chain1, pipe2); 
     Chain<String, Long, BigInteger> chain3 = Chain.append(chain2, pipe3); 
     List<BigInteger> result = chain3.run(Collections.singletonList("1")); 
     System.out.println(result); 
    } 
} 
+0

Obviamente, si puede usar Java 8, entonces deseará usar Streams en su lugar. O algo así como RxJava. – Shannon