web-dev-qa-db-ja.com

状態や動作を追加せずにサブタイピング-悪い習慣?

観察

状態や動作が追加されていないExceptionサブタイプはたくさんあります。例: ClosedByInterruptException コード

質問

Exceptionの場合、状態または動作を追加せずにサブタイピングを「許容」するのはなぜですか?私が知っていることから、新しい動作を定義するときはinterfaceを使用し、動作を実装するときや状態を追加するときはclassを使用する必要があります(動作がオーバーライドされる可能性があります)。 Exceptionタイプ以外は、適用できますか?

markers を例として使用しないでください。

詳細

以下は、眉をひそめているデザインです:

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}

abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

   //...assume methods that use String name are not overridable
}

これがなぜ眉をひそめられるか理解できない人のために、ポストの下部を見なさい。

理解している人にとっては、これはClosedByInterruptExceptionと同じカテゴリに分類されます。サブタイプは新しい状態や動作を定義しません。では、デザインが不快な場合、なぜこのような例外が存在するのでしょうか?

例外はより説明的ですが、Stringメッセージを介して拡張説明を含めることができます。ソケットがSocketExceptionまたはSocketClosedExceptionではなく早期に閉じられた場合にSocketNotConnectedExceptionがどのようにスローされるかを見て、なぜ新しいAPIは問題のデザインを使用するのですか?


デザインがなぜ眉をひそめられるのか疑問に思う方のために

CatDogの例を見ると、新しい動作が追加されておらず、動作も上書きされていないため、どちらもまったく同じ方法で実行されます。唯一の違いは、instanceofによる型チェックです。 instanceofを使用する場合、タイプに基づいて次のような動作を実行しようとしています。

Animal animal = ...;

if(animal instanceof Dog) {
    //perform some behavior
} else if(animal instanceof Cat) {
    //perform some behavior
}

このような場合、ビヘイビアーはメソッドをオーバーライドすることによって型内に含まれる必要があり、ビヘイビアーはポリモーフィズムを介してトリガーされる必要があります。

7
Vince Emigh

Javaの例外処理メカニズムは、クラス階層を使用して、例外ケースの分類法をモデル化します。

  • キャッチフレーズはinstanceofテストを効果的に行います。したがって、彼らはClosedByInterruptExceptionをより一般的なケースAsynchronousCloseExceptionClosedChannelExceptionなどと区別できます。
  • Throwable#toString()にはクラス名が含まれます。したがって、スタックトレースバックは、発生した特定のタイプの例外を示します。
  • throws句は、指定された例外タイプをそのより一般的なケース(スーパークラスとして表される)と区別します。

あなたが参照した設計ガイドラインは、通常のオブジェクト指向の方法でインスタンスを扱う一般的なケースに適用されます-状態、動作、およびアイデンティティを持つものとして。メッセージを送信してください。

ただし、クラス階層をモデリングツールとして使用する場合もあります。別の例としては、データベースのインスタンスの読み取りと書き込みがあります。データベースI/Oフレームワークは、クラスをデータベーステーブルとペアにすることができます。

16
Jerry101

例外が提案された方法で実装される場合、例外ハンドラーが存在するすべてのレベルで例外をキャッチして評価する必要があることを意味します。これは、現在の目的と魅力の大部分を打ち負かし、最終的にエラーコードを返し、コールスタックのすべてのレベルでそれらを評価することよりもはるかに優れているでしょう。したがって、OOの観点からは、多分それはクラッジですが、利点はかなりあります。

アニマルキャットの例については、これらの種類の違反は一貫性と実際的な理由のためにしばしば行われます。まだすべての具体的な動物を完全に実装しているわけではないかもしれませんが、たとえばビジターパターンを使用する場合のように、プログラム構造には1つのジェネリッククラスと拡張機能が必要です。

あなたの観察はおそらく、多くの人々が問題ドメインレベルのタイプの問題に対してカスタム例外クラスを作成することに煩わされることができず、この悪い習慣が非常に一般的であるという事実の説明です。

1
Martin Maat

例外の場合に、状態や動作を追加せずにサブタイピングを「許容」するのはなぜですか?私が知っていることから、新しい動作を定義するときはインターフェースを使用し、動作を実装するときや状態を追加するときはクラスを使用する必要があります。

デフォルトのメソッド はJava=リリース8でのみ追加されました。Exceptionクラスはリリース1からソースに含まれているため、リリース8までクラスが使用されない限り、機能をツリーに渡す方法はありませんでした。

例外のインターフェイスを使用すると、コピーパスタコードが生成されます。例外ごとにロジックを再実装する必要があります(違いはありません)。クラスを使用すると、1つまたは2つのコンストラクターを使用できます。

Throwableから拡張することで、状態を追加します。スタックトレースは、発生した特定の例外を示し、いくつかのメソッドはtoString()などの例外の名前も返します。


ウィキペディアの記事 マーカーインターフェイスパターン

マーカーインターフェイスの主な問題は、インターフェイスがクラスを実装するためのコントラクトを定義し、そのコントラクトがすべてのサブクラスによって継承されることです。つまり、マーカーを「実装解除」することはできません。与えられた例で、シリアライズしたくないサブクラスを作成する場合(一時的な状態に依存している可能性があるため)、明示的にNotSerializableExceptionをスローする必要があります(ObjectOutputStreamドキュメントごとに)

これは、Java.lang.Throwableのクラス定義です。

public class Throwable implements Serializable

猫と犬の例を見ると、新しい動作は追加されておらず、動作も上書きされていないため、どちらもまったく同じ方法で実行されます。唯一の違いは、instanceofによる型チェックです。

Javaのtry/catchメカニズムは、instanceofに大きく依存しています。これにより、広範な例外IOException、または特定の例外ArrayIndexOutOfBoundsをキャッチできます。まれに特定の例外が存在し、例外に新しい機能を追加できます。これは、「メソッドをオーバーライドして、ポリモーフィズムによって動作をトリガーする」ことほど簡単ではありません。

デザインパターンは行き来します。パターンがどこかで使用されている、または何かが特定の方法で実装されているからといって、たとえJDK公式ソースであっても、それがであるとは限りません 。それはその時だったかもしれないし、おそらく誰もがこの方法を実装することを考えていなかったかもしれません。

0
Luke Melaia

私はそれが悪い習慣だとは思わない。それはデザインと呼ばれています:

  • タイプの階層または一連のインターフェースをモデル化して、ジュニアプログラマーに出発点を与えることができます。
  • ソフトウェアは1日で作成されないため、追加の動作や状態のないサブタイプを作成して明日完了することもできます。これは、一連のインターフェイスを作成してデザインのスケルトンを作成し、翌日からそれらを具体化するのと同じ方法です。
  • Javaの例外の場合、特定の例外をキャッチできるので非常に便利だと思います。それ以外の場合は、ケース構造を使用してスローされた例外を判別します。

記録のためだけに:継承と抽象クラスよりも構成とインターフェースを好みますが、それらには場所と用途があり、Java例外処理は継承プットの例ですサブクラスによって追加の動作や状態が追加されない場合でも、有効に活用できます。

0