2011-12-30 11 views
5

Ho avuto problemi con il seguente problema. Ho una serie di oggetti funzione, ognuno con i propri tipi di input e output definiti tramite argomenti di tipo generico in java. Vorrei sistemarli in una catena in modo che i dati grezzi siano immessi nella prima funzione, trasformati nel tipo di output, che è il tipo di input dell'oggetto successivo, e così via. ovviamente questo sarebbe banale per l'hard-code, ma mi piacerebbe che il codice fosse collegabile ai nuovi oggetti funzione. se ho appena lasciare fuori argomenti di tipo (solo il tipo di output finale), questo è come stanno le cose:Java Generics: concatenamento di un oggetto funzione generico

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

qui un iteratore sui dati viene passato tra gli oggetti di funzione, e il contesto dovrebbe essere Contesto. c'è un modo per mantenere il seguente tipo di tubo collegabile e mantenere ancora il tipo di sicurezza?

modifica: per chiarezza, ho una serie di oggetti funzione, tubi. ognuno prende come input un tipo particolare e emette un altro tipo. (in realtà un iteratore su questi tipi) questi saranno incatenati insieme, ad esempio, Pipe<A,B> -> Pipe<B,C> -> Pipe<C,D> -> ..., in modo che l'output di una pipe sia il tipo di input per la pipe successiva. C'è anche una fonte qui che emette un iteratore di tipo A, e un sink che accetta il tipo (l'output del pipe passato). questo rende le cose più chiare? La domanda è, perché c'è una dipendenza critica dalla compatibilità dei tipi di input e output, c'è un modo per garantire questo?

Sto iniziando a pensare che sull'inserimento degli oggetti funzione nella pipeline potrebbe essere il momento migliore per garantire la sicurezza del tipo, ma non sono sicuro di come farlo.

Edit 2: Ho un metodo valore aggiuntivo per gli oggetti funzione che attualmente si presenta come di seguito:

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

mi piacerebbe verificare se il primo parametro tipo è la stessa come la "fine" di la pipa corrente e lanciare un'eccezione se non? non penso che ci sia un buon modo per ottenere la sicurezza del tempo di compilazione qui. la "fine" del tubo corrente può quindi essere impostata sul secondo tipo param del tubo di ingresso. Non riesco a pensare a come farlo con i generici, e passare le informazioni di classe mi sembra piuttosto orribile.

+1

La cancellazione di tipo può rendere la vita più difficile per funzioni completamente generiche. Il compilatore di byte JVM rimuove il tipo dall'istanza delle classi generiche, quindi l'Elenco diventa List . –

+0

Puoi spiegare un po 'di più il requisito? Incapace di capire cosa hai e cosa vuoi avere. –

+0

quindi, l'approccio migliore sarebbe cercare di garantire la sicurezza del tipo quando si inserisce l'oggetto funzione pipe (how ??) e si limitano a sopprimere gli avvertimenti su questo metodo sopra? – downer

risposta

7

Ecco un modo per farlo. Il metodo run non è typesafe, ma dato che l'unico modo per aggiungere un pipe è farlo in un modo sicuro dal tipo, l'intera catena è safe-type.

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

davvero bello, e come tutte le buone soluzioni, è ovvio una volta che è noto :) – downer

+0

Grazie per la tua risposta :) Creo un [progetto github] (https://github.com/smartorigin/Dynamic-Workflow) basato sul tuo risposta che aggiunge funzionalità asincrona ed esecuzione parallela. Spero che possa aiutare qualcuno. –

0

Ecco un altro modo per farlo: in questo modo è possibile trasformare un passaggio in un elenco. Ad esempio, una trasformazione potrebbe dividere una stringa in più sottostringhe. Inoltre, consente un codice di gestione delle eccezioni comune se la trasformazione di uno qualsiasi dei valori produce un'eccezione. Consente inoltre di utilizzare un elenco vuoto come valore di ritorno anziché un valore nullo ambiguo per cui è necessario verificare per evitare NullPointerException. Il problema principale con questo è che esegue ogni passaggio di trasformazione nella sua interezza prima di passare al passaggio successivo, che potrebbe non essere efficiente in termini di 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

Ovviamente, se sei in grado di usare Java 8, allora preferiresti usare gli Stream. O, qualcosa come RxJava. – Shannon