8

以下の問題で悩んでいます。一連の関数オブジェクトがあり、それぞれがJavaのジェネリック型引数を介して定義された独自の入力型と出力型を持っています。生データが最初の関数に入力され、次のオブジェクトの入力型である出力型に変換されるように、これらをチェーンに配置したいと思います。もちろん、これはハードコードするのは簡単ですが、コードを新しい関数オブジェクトにプラグインできるようにしたいと思います。型引数 (最終的な出力型のみ) を省略すると、次のようになります。

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

ここでは、データに対する反復子が関数オブジェクト間で渡され、コンテキストは Context である必要があります。次の種類のパイプをプラグ可能に保ち、タイプの安全性を維持する方法はありますか?

編集:わかりやすくするために、一連の関数オブジェクト、パイプがあります。それぞれが特定の型を入力として受け取り、別の型を出力します。(実際には、これらの型のイテレータ) これらは連鎖されます。たとえば、Pipe<A,B> -> Pipe<B,C> -> Pipe<C,D> -> ...1 つのパイプの出力が次のパイプの入力型になるようにします。ここには、タイプ A の反復子を出力するソースと、タイプ (過去のパイプの出力) を受け入れるシンクもあります。これは物事をより明確にしますか?問題は、入力と出力の型の互換性に大きく依存しているため、これを保証する方法はあるのでしょうか?

関数オブジェクトをパイプラインに挿入するときが、型の安全性を確保するのに最適な時期かもしれないと考え始めていますが、これを行う方法がわかりません。

編集 2: 現在、以下のような関数オブジェクトの adder メソッドがあります。

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

最初の型パラメーターが現在のパイプの「終了」と同じかどうかを確認し、そうでない場合は例外をスローしますか? ここでコンパイル時の安全性を確保する良い方法があるとは思いません。現在のパイプの「終了」は、入力パイプの 2 番目のタイプのパラメーターに設定できます。ジェネリックでこれを行う方法が思い浮かびません。また、クラス情報を渡すのは非常に恐ろしいようです。

4

2 に答える 2

12

ここにそれを行う方法があります。run メソッドはタイプ セーフではありませんが、パイプを追加する唯一の方法はタイプ セーフな方法で行うことであるため、チェーン全体がタイプ セーフです。

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);
    }
}
于 2011-12-30T16:48:12.343 に答える
1

これを行う別の方法を次に示します。この方法では、変換ステップをリストにすることができます。たとえば、変換によって文字列が複数の部分文字列に分割される場合があります。さらに、値のいずれかを変換すると例外が発生した場合に、共通の例外処理コードを使用できます。また、NullPointerException を回避するためにテストする必要があるあいまいな null 値の代わりに、空の List を戻り値として使用することもできます。これの主な問題は、次のステップに移動する前に各変換ステップを完全に実行することであり、メモリ効率が悪い可能性があります。

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);
    }
}
于 2014-04-08T17:18:40.957 に答える