複数のインターフェイスを結合する空のインターフェイス


20

次の2つのインターフェイスがあるとします。

interface Readable {
    public void read();
}

interface Writable {
    public void write();
}

場合によっては、実装オブジェクトはこれらの1つしかサポートできませんが、多くの場合、実装は両方のインターフェイスをサポートします。インターフェイスを使用する人々は次のようなことをしなければなりません:

// can't write to it without explicit casting
Readable myObject = new MyObject();

// can't read from it without explicit casting
Writable myObject = new MyObject();

// tight coupling to actual implementation
MyObject myObject = new MyObject();

これらのオプションはどれも非常に便利ではありません。これをメソッドパラメーターとして使用することを考えると、さらに便利です。

1つの解決策は、ラッピングインターフェイスを宣言することです。

interface TheWholeShabam extends Readable, Writable {}

リード・ライト可能の両方がサポートするすべての実装:しかし、これはある特定の問題がある持っている、彼らはインターフェイスを使用している人々との互換性を持つようにしたい場合はTheWholeShabamを実装することを。たとえ両方のインターフェースの存在が保証されていること以外は何も提供しませんが。

この問題に対する明確な解決策はありますか、それともラッパーインターフェイスを使用する必要がありますか?

更新

実際には、読み取りと書き込みの両方が可能なオブジェクトを用意する必要がある場合が多いので、単に引数の懸念を分離するだけでは必ずしもきれいな解決策とは限りません。

更新2

(回答として抽出されているため、コメントしやすくなっています)

更新3

これの主なユースケースストリームではないことに注意してください(ただし、それらもサポートする必要があります)。ストリームは入力と出力を非常に明確に区別し、責任の明確な分離があります。むしろ、非常に特定の状態がアタッチされている1つのオブジェクトに書き込みと読み取りができる1つのオブジェクトが必要なバイトバッファのようなものを考えてください。これらのオブジェクトは、非同期I / O、エンコードなどのいくつかの場合に非常に役立つために存在します。

更新4

私が最初に試したものの1つは、以下に示す提案と同じでした(受け入れられた答えを確認してください)が、あまりにも壊れやすいことが判明しました。

型を返す必要があるクラスがあるとします:

public <RW extends Readable & Writable> RW getItAll();

このメソッドを呼び出す場合、ジェネリックRWはオブジェクトを受け取る変数によって決定されるため、この変数を記述する方法が必要です。

MyObject myObject = someInstance.getItAll();

これは機能しますが、もう一度実装に結び付け、実行時にクラスキャスト例外をスローする場合があります(返される内容によって異なります)。

さらに、RW型のクラス変数が必要な場合は、クラスレベルでジェネリックを定義する必要があります。


5
フレーズは「シバン全体」
ケビンクライン

これは良い質問ですが、サンプルインターフェイスとしてReadableと 'Writable'を使用すると、通常は異なる役割であるため、多少
濁って

@Basuetaネーミングが簡略化されましたが、実際には読み取りと書き込みがユースケースをかなりうまく伝えています。場合によっては読み取り専用、一部の場合は書き込み専用、驚くほど多くの場合は読み取りと書き込みが必要です。
nablex

読み取りと書き込みの両方が可能な単一のストリームが必要になったときのことを考えることはできません。あまり議論の余地のないインターフェイスのペアを選択する方が役立つかもしれないと言っています
...-vaughandroid

@Baqueta java.nio *パッケージと何か関係がありますか?ストリームにこだわる場合、ユースケースは確かにByteArray * Streamを使用する場所に限定されます。
nablex

回答:


19

はい、メソッドパラメータを、Readableとの両方を拡張する指定不足の型として宣言できますWritable

public <RW extends Readable & Writable> void process(RW thing);

メソッド宣言はひどいように見えますが、統一されたインターフェースについて知る必要があるよりも、それを使用する方が簡単です。


2
ここでは、Konradsの2番目のアプローチをお勧めします。process(Readable readThing, Writable writeThing)また、を使用して呼び出す必要がある場合process(foo, foo)
ヨアヒムザウアー

1
正しい構文ではありません<RW extends Readable&Writable>か?
から来る

1
@JoachimSauer:視覚的にvisuallyいだけのアプローチよりも簡単に破れるアプローチを好むのはなぜですか?process(foo、bar)を呼び出し、fooとbarが異なる場合、メソッドは失敗する可能性があります。
マイケルショー

@MichaelShaw:私が言っているのは、それらが異なるオブジェクトである場合に失敗してはならないということです。なぜそれが必要ですか?もしそうなら、私はそれprocessが複数の異なることをし、単一責任の原則に違反していると主張します。
ヨアヒムザウアー

@JoachimSauer:なぜ失敗しないのですか?for(i = 0; j <100; i ++)はfor(i = 0; i <100; i ++)ほど有用なループではありません。forループは同じ変数の読み取りと書き込みの両方を行いますが、SRPに違反することはありません。
マイケルショー

12

あなたが必要な場所場所がある場合はmyObject、両方のAとReadableし、Writable次のことができます。

  • その場所をリファクタリングしますか?読み取りと書き込みは異なるものです。メソッドが両方を実行する場合、おそらく単一の責任原則に従っていません。

  • myObjecta Readableおよびa Writable(2つの引数)として2回渡します。メソッドは、それが同じオブジェクトであるかどうかを何に気にしますか?


1
これを引数として使用すると機能する可能性があり、それらは別個の懸念事項です。ただし、読み取りと書き込みを同時に実行できるオブジェクトが必要な場合があります(たとえば、ByteArrayOutputStreamを使用するのと同じ理由で)
nablex

どうして?名前が示すように、出力ストリームは書き込みます-読み取り可能な入力ストリームです。C#で同じ-がありますStreamWriterStreamReader(および他の多くは)
コンラートMorawski

バイトを取得するためにByteArrayOutputStreamに書き込みます(toByteArray())。これは、write + readと同等です。インターフェースの背後にある実際の機能はほとんど同じですが、より一般的な方法です。読み取りまたは書き込みのみが必要な場合もあれば、両方が必要な場合もあります。別の例はByteBufferです。
nablex

2
私はその2番目の点で少し反動しましたが、しばらく考えた後、それは悪い考えとは思えません。読み取りと書き込みを分離するだけでなく、より柔軟な関数を作成し、変換する入力状態の量を減らします。
フォシ

2
@Phoshi問題は、懸念が必ずしも分離されないことです。読み取りと書き込みの両方が可能なオブジェクトが必要な場合があり、それが同じオブジェクトであるという保証が必要な場合(たとえば、ByteArrayOutputStream、ByteBuffer、...)
nablex

4

現在、読み取りまたは書き込みが必要ではなく、両方が必要な状況に対処する回答はありません。Aに書き込むとき、Aに書き込むのではなく、Aからデータを読み取ることができ、Bから読み取ることができ、実際には同じオブジェクトであることを保証する必要があります。ユースケースは豊富です。たとえば、ByteBufferを使用するすべての場所です。

とにかく、作業中のモジュールはほぼ完成しており、現在、ラッパーインターフェイスを選択しています。

interface Container extends Readable, Writable {}

これで、少なくとも次のことができます。

Container container = IOUtils.newContainer();
container.write("something".getBytes());
System.out.println(IOUtils.toString(container));

コンテナの現在の実装(現在3)はすべて、個別のインターフェイスではなくコンテナを実装していますが、実装でこれを忘れてしまった場合、IOUtilsはユーティリティメソッドを提供します。

Readable myReadable = ...;
// assuming myReadable is also Writable you can do this:
Container container = IOUtils.toByteContainer(myReadable);

私はこれが最適なソリューションではないことを知っていますが、コンテナはまだかなり大きなユースケースであるため、現時点ではまだ最善の方法です。


1
これはまったく問題ないと思います。他の回答で提案されている他のアプローチよりも優れています。
トムアンダーソン

0

汎用のMyObjectインスタンスを指定すると、読み取りまたは書き込みをサポートするかどうかを常に知る必要があります。したがって、次のようなコードがあります。

if (myObject instanceof Readable)  {
    Readable  r = (Readable) myObject;
    readThisReadable( r );
}

単純な場合、これを改善できるとは思いません。しかし、Readableを読み込んだ後に別のファイルにReadable readThisReadable書き込みたい場合は、扱いにくくなります。

だから私はおそらくこれで行くだろう:

interface TheWholeShabam  {
    public boolean  isReadable();
    public boolean  isWriteable();
    public void     read();
    public void     write();
}

これをパラメータとして使用するとreadThisReadablereadThisWholeShabamMyObjectだけでなく、TheWholeShabamを実装するクラスを処理できるようになりました。 そして、書き込み可能な場合は書き込むことができ、書き込み可能でない場合は書き込むことができません。(実際の「ポリモーフィズム」があります。)

したがって、最初のコードセットは次のようになります。

TheWholeShabam  myObject = ...;
if (myObject.isReadable()
    readThisWholeShebam( myObject );

また、readThisWholeShebam()に可読性チェックを実行させることで、ここに行を保存できます。

これは、以前のReadable-onlyがisWriteable()(falseを返す)とwrite()(何もしない)を実装する必要があることを意味します。オブジェクトは、それ以上の努力をせずに対処します。

もう1つ:読み取りを行わないクラスでread()の呼び出し、書き込みを行わないクラスでwrite()の呼び出しを処理できる場合、isReadable()およびisWriteableをスキップできます。 ()メソッド。これは、うまく機能する場合、これを処理する最もエレガントな方法です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.