OOD:Java継承とキャストによる子メソッドへのアクセス


8

私はいくつかのクラス持っているParentとしChild1... Child9Javaで実装されています。Parent抽象クラスであり、子クラスのすべての共通変数(多くのParent場合、インターフェースではなく抽象クラスを作成した主な理由です)、いくつかの抽象メソッド、およびいくつかの実装されたメソッドを含みます。

一部の子クラスには、それらに固有のカスタムメソッドがあります。ダウンキャストを使用して子メソッドを呼び出すことがよくあります。

Parent p = new Child1();
((Child1) p).child1SpecificMethod();

どういうわけか、これはOODの悪い習慣だと感じていますが、それが本当にデザインの改善方法であるかどうかはよくわかりません。

-編集- とにかく変更する必要があるのは、多くの(今のところ)共通変数を整理するためにParentクラスを使用し、それらを具象クラスのメンバー(またはコンテナーオブジェクト)にすることです。


6
pの値がChild1型であることを知っている場合は、pをChild1型にして、キャストする必要がないようにしてください。
Benni 2014年

2
一般的にはい、これは悪い習慣です。あなたが自分をよくダウンキャストしていることに気づいたら、おそらく何か間違ったことをしたでしょう。しかし、状況についてこれ以上の情報がないと、これ以上言うのは本当に難しいです。
2014年

4
そもそもなぜとして宣言しpているのParentですか?一般的なタイプに固執することは、APIと戻り値のタイプの良い習慣ですが、具象クラスを自分で割り当てる同じスコープ内にはありません。
Kilian Foth、2014年

ユースケースへの継承が困難なようです。子供が継承を使用して共通の親を利用できない場合は、クラスの設計を確認する必要があります
kolossus

回答:


11

これは悪い習慣であるだけでなく、不必要に複雑です。

なぜ一般的に継承を使うのですか?

継承を使用すると、多くの異なるキャリアで利用できるようにする共通の動作セットがあります。これには、クラスの継承とalsのインターフェイスの継承が含まれます相続人は、いわば、しばしばある専門、それは継承誰からクラスの。これは主にクラスの継承に当てはまります。

クラスカーとサブクラスのポルシェを考えてみてください(典型的なのは-関係です)。エンジンの始動/停止ステアリングなどの一般的な動作があります。ポルシェを車のように扱うと、その挙動のこの側面に縛られます。ポルシェだけが必要で、それをポルシェとして扱うだけであることがわかっている場合、ポルシェとしてインスタンス化し、キャストによってポルシェの動作を取得することは冗長です。

ポリモーフィズムは、その逆の意味を持ちます。

あなたはポルシェを持っていて、車の観点からそれを扱う必要があります。例えば運転

限り、あなたのようポルシェが受け入れステアが左右の操舵をシフトアップシフトダウンあなたが他のための多型/置換1つの使用を作ることができなど、。

オブジェクトを特殊な形式でインスタンス化することをお勧めします。そうすれば、ポリモーフィズムを最大限に活用して、必要なときにだけ使用することができます。

つまりParent p = new Child1();、私には意味がありません。

編集:私はポルシェを別の方法で(コンポジションを介して)実装しますが、例として、それ車です。


4

あなたの気持ちはそれを悪い習慣だと考えるのは正しいことです。あなたのサンプルコードが少し違うと想像してください:

Parent p = createObject();
((Child1) p).child1SpecificMethod();

pの値が本当にChild1であることをどうやって知っていますか?そうしないと、実行時にClassCastExceptionが発生する可能性があります。コードでchild1SpecificMethod()を呼び出す必要がある場合は、pのタイプがChild1であることを確認する必要があります。Object pがタイプParentとしてコードに(たとえば、メソッドパラメーターとして)渡されるためにそれが不可能な場合は、Visitor-Patternのバリアントを使用して、ビジターオブジェクトのhandle-Methodでchild1SpecificMethodを実行することを検討できます。子供1。


「pのタイプがChild1であることを確認する必要があります。オブジェクトpがメソッドパラメータとして渡されるため、実際には不可能です。ビジターパターンは良いヒントです。
jpmath 2014年

4

代わりに機能の検索を使用してください。 子クラスへのアクセスを与えないでください、それらは親クラスの実装を考慮してください。

次に、いくつかの機能、機能を指定するインターフェースを定義します。

interface Child1Specific {
    void child1SpecificMethod();
}

使用法:

Parent parent = ...
Child1Specific specific = parent.lookup(Child1Specific.class);
if (specific1 != null) {
    specific1.child1SpecificMethod();
}

この検出メカニズムは非常に柔軟です。継承の代わりに委任を使用すると、かなりやりがいがあります。子クラスを持つ必要がなくなったことに注意してください。

またはjava 8(いくつかのバリエーションが可能であり、インターフェースも機能する可能性がある場合):

Optional<Child1Specific> specific = parent.lookup(Child1Specific.class);
if (specific1.isPresent()) {
    specific1.get().child1SpecificMethod();
}

Parentクラスで、いくつかの機能のルックアップを作成します。

public class Parent {
    protected final Map<Class<?>, Object> capabilities = new HashMap<>();
    protected final <T> void registerCapability(Class<T> klass, T object);

    public <T> T lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return object == null ? null : klass.cast(object);
    }

またはJava 8では:

    public <T> Optional<T> lookup(Class<T> klass) {
        Object object = capabilities.get(klass);
        return Optional.ofNullable(klass.cast(object));
    }

子クラス:

class Child1 extend Parent implements Child1Specific {
    Child1() {
        registerCapability(Child1Specific.class, this);
    }
}

またはより動的:

class Child1 extends Parent {
    private Child1Specific specific = new Child1Specific() {
        ... Parent.this ...
    };
    Child1() {
        registerCapability(Child1Specific.class, specific);
    }
}

-3

親クラスに抽象メソッドchild1SpecificMethod()を追加し(クラスを抽象としてマークする必要があります)、それぞれの子クラスでその実装を提供します。

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