2011-12-30 11 views
5

Mam problem z następującym problemem. Mam serię obiektów funkcji, każdy z własnymi typami wejściowymi i wyjściowymi zdefiniowanymi za pomocą ogólnych argumentów typu w java. Chciałbym rozmieścić je w łańcuchu, tak aby surowe dane były wprowadzane do pierwszej funkcji, przekształcane do postaci wyjściowej, która jest typem wejściowym następnego obiektu i tak dalej. Oczywiście byłoby to trywialne w przypadku twardego kodu, ale chciałbym, aby kod mógł być podłączony do nowych obiektów funkcji. jeśli po prostu pominąć argumenty typu (tylko ostatni typ wyjścia), to jak wyglądają sprawy:Java Generics: łączenie razem obiektu funkcji ogólnej

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

tutaj iterator nad dane są przekazywane między obiektami funkcyjnych, a kontekst powinien być kontekst. czy istnieje sposób na utrzymanie tego rodzaju rur wtykowych i nadal utrzymują bezpieczeństwo typu?

edytuj: dla przejrzystości, mam serię obiektów funkcji, potoków. każdy przyjmuje jako dane wejściowe określonego typu i wyprowadza inny typ. (w rzeczywistości Iteratory nad tymi typami) będą one połączone razem, np. Pipe<A,B> -> Pipe<B,C> -> Pipe<C,D> -> ..., tak, że wyjście z jednej rury jest typem wejściowym dla następnej rury. Jest tu również źródło, które wyprowadza iterator typu A i zlew, który zaakceptuje typ (wyjście poprzedniej rury). czy to sprawia, że ​​rzeczy są wyraźniejsze? Pytanie brzmi, ponieważ istnieje krytyczna zależność od kompatybilności typów wejść i wyjść, czy istnieje sposób, aby to zapewnić?

Zaczynam myśleć, że wstawienie obiektów funkcji do potoku może być najlepszym momentem do zapewnienia bezpieczeństwa typu, ale nie jestem pewien, jak to zrobić.

edit 2: Mam metodę adder dla obiektów funkcyjnych, które obecnie wygląda jak poniżej:

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

chciałbym sprawdzić, czy pierwszy parametr typ jest taka sama jak „końca” bieżącą potokiem i wyrzucić wyjątek, jeśli nie? Nie sądzę, jest tu dobry sposób, aby uzyskać tutaj czas kompilacji bezpieczeństwa. "koniec" bieżącej rury można następnie ustawić na drugi typ rury wejściowej. Nie mogę myśleć o tym, jak to zrobić z lekami generycznymi, a przekazywanie informacji o klasie wydaje się dość odrażające.

+1

Usunięcie tekstu może utrudnić życie w pełni w pełni funkcjami ogólnymi. Kompilator bajtów JVM usuwa typ z instancji klas ogólnych, więc lista staje się listą . –

+0

Czy możesz wyjaśnić ten wymóg trochę więcej? Nie można ustalić, co masz i co chcesz mieć. –

+0

więc najlepiej byłoby spróbować zapewnić bezpieczeństwo typu podczas wstawiania obiektu funkcji rury (jak?) I po prostu tłumić ostrzeżenia w powyższej metodzie? – downer

Odpowiedz

7

Oto sposób na zrobienie tego. Metoda run nie jest bezpieczna dla typów, ale biorąc pod uwagę, że jedynym sposobem dołączenia rury jest wykonanie jej w bezpieczny sposób, cały łańcuch jest bezpieczny dla typu.

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

Naprawdę fajnie, i jak wszystkie dobre rozwiązania, to jest oczywiste, gdy już jest znana :) – downer

+0

Dziękuję za odpowiedź :) Tworzę projekt [github project] (https://github.com/smartorigin/Dynamic-Workflow) na podstawie Twojego odpowiedź, która dodaje funkcjonalność asynchroniczną i wykonywanie równoległe. Mam nadzieję, że to komuś pomogłoby. –

0

Oto inny sposób, aby to zrobić: w ten sposób pozwala na krok transformacji, aby uzyskać listę. Na przykład transformacja może podzielić łańcuch na wiele podłańcuchów. Co więcej, pozwala na wspólny kod obsługi wyjątków, jeśli transformacja dowolnej wartości powoduje wyjątek. Umożliwia także użycie pustej listy jako wartości zwracanej zamiast niejednoznacznej wartości pustej, która musi zostać przetestowana, aby uniknąć wyjątku NullPointerException. Głównym problemem z tym jest to, że robi każdy krok transformacji w całości przed przejściem do następnego kroku, który może nie być efektywny pod względem pamięci.

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

Oczywiście, jeśli jesteś w stanie używać Java 8, zamiast tego chcesz użyć strumienia. Lub coś w stylu RxJava. – Shannon