2

クラス階層をレイアウトするとき、コードを共有しながら機能をカプセル化できることのギャップに不満を感じることがよくあります。もちろん、問題の一部は多重継承の欠如ですが、インターフェイスはいくらか役に立ちます。インターフェイスで保護されたメソッドを定義できないことが、より大きな問題のように思えます。

標準的な解決策は、保護された抽象基本クラスによって実装されるパブリック インターフェイスを持つようです。問題は、次の場合です

public interface Foo {
    public String getName();
}

abstract protected BaseFoo implements Foo {
    abstract protected int getId();

    private String name;
    protected BaseFoo(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

public class ConcreteFoo extends BaseFoo {
    public ConcreteFoo (String name) {
        super(name);
    }

    @Override
    protected int getId() {
        return 4; // chosen by fair dice roll.
                  // guaranteed to be random.
    }
}

// in the foo package with the classes above
public class FooCollection {
    private static Map<Integer, Foo> foos = new HashMap();
    public static void add(Foo foo) {
        synchronized(foos) {
            foos.put(foo.getId(), foo); // can't call foo.getId()
        }
    }
}

// client code, not in the foo package
FooCollection.add(new ConcreteFoo("hello world"));

つまり、適切にカプセル化されたオブジェクトの 1 つを呼び出し元に返しますが、そのオブジェクトを取得するメソッドは、何らかの内部機能に依存できる必要があります。その内部機能をインターフェイスの一部にすることはできませんが (カプセル化が壊れる可能性があります)、抽象基本クラスの一部にするには、キャストを使用する必要があります。

Foo を抽象クラスにすることはできません。これは、他のインターフェイスがそれを拡張して、ここに表示されているよりも複雑な階層にオプションの直交機能を追加する必要があるためです。

この問題に対する標準的なアプローチは何ですか? クライアントが使用すべきではないにもかかわらず、getId を Foo インターフェースに追加しますか? FooCollection.add で BaseFoo への安全でないキャストを実行しますか? キャストする前に確認する場合、すべての意図と目的のために常に型が一致する必要があるにもかかわらず、型が一致しない場合はどうしますか?

この種の状況でのベスト プラクティスに関する情報は、非常に役立ちます。

編集:明確でない場合のために、この例は意図的に単純化されています。重要な点は、オブジェクトの「インターフェイス ビュー」を返す場合があるということです。その「インターフェイス ビュー」がパッケージ固有のクラスに戻される場合、渡されるメソッドは、その実装で内部機能を使用する必要がある可能性があります。内部機能と公開機能の不一致をどのように管理しますか?

4

2 に答える 2

6

さて、ここにいくつかのポイントがあります:

  1. 一般的な意見とは反対に、継承は実際にはコードを共有することではありません。継承階層で作成するものは、一連の共通の抽象的な動作を共有するものの編成です。一部のコードを再利用する効果がある場合があります。

  2. ここ数年でファッションが大きく変化したため、深く複雑な継承階層はもはや良い形とは見なされなくなりました。一般的にJavaで。あなたがすべき

    • インターフェイスを実装する前に集約を使用する
    • インターフェイスを使用して「ミックスイン」コントラクトを表現する
    • クラスが自然継承を持つものを記述している場合にのみ、継承を使用してください。
  3. 多重継承の効果が本当に必要な場合は、インターフェイスの実装クラスを作成し、それらを集約します。

  4. 特に、インターフェイスと実装クラスを使用してクラスを定義することにより、テストの作成がはるかに簡単になります。インターフェイスが分離されている場合、そのインターフェイスのモックを作成するのはほとんど簡単です。

于 2012-04-24T23:54:04.820 に答える
2

「ベスト」プラクティスについてはわかりませんが、いくつかのアイデアがあります。

インターフェイスは、「何をすべきか」と「何かをどのようにすべきか」を分離することになっています。ゲッターとセッターはインターフェイスに属していないと思います。私は彼らにもっと意味のある署名を与えるようにしています。

あなたの場合、次の 2 つのインターフェイスに問題はありません。

public interface Nameable {
    String getName();
}

public interface Identifiable {
    int getId();
}

2つを分離します。クライアントに必要なものだけを実装させる。抽象クラスの一部にするかどうかの決定idは任意です。それを分離して明確にすることは役に立ちます。

キャスティングはポリモーフィズムの利点をすべて失います。安易に手放すべきではないと思います。getId()インターフェイスに移動する必要がある場合は、移動してください。別の選択肢で回避できる場合は、そうしてください。

「最高」は文脈によって異なります。あなたの単純な例は、すべての場合に当てはまるとは限りません。

于 2012-04-24T23:54:42.473 に答える