観察
状態や動作が追加されていない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は問題のデザインを使用するのですか?
デザインがなぜ眉をひそめられるのか疑問に思う方のために
Cat
とDog
の例を見ると、新しい動作が追加されておらず、動作も上書きされていないため、どちらもまったく同じ方法で実行されます。唯一の違いは、instanceof
による型チェックです。 instanceof
を使用する場合、タイプに基づいて次のような動作を実行しようとしています。
Animal animal = ...;
if(animal instanceof Dog) {
//perform some behavior
} else if(animal instanceof Cat) {
//perform some behavior
}
このような場合、ビヘイビアーはメソッドをオーバーライドすることによって型内に含まれる必要があり、ビヘイビアーはポリモーフィズムを介してトリガーされる必要があります。
Javaの例外処理メカニズムは、クラス階層を使用して、例外ケースの分類法をモデル化します。
instanceof
テストを効果的に行います。したがって、彼らはClosedByInterruptException
をより一般的なケースAsynchronousCloseException
、ClosedChannelException
などと区別できます。Throwable#toString()
にはクラス名が含まれます。したがって、スタックトレースバックは、発生した特定のタイプの例外を示します。throws
句は、指定された例外タイプをそのより一般的なケース(スーパークラスとして表される)と区別します。あなたが参照した設計ガイドラインは、通常のオブジェクト指向の方法でインスタンスを扱う一般的なケースに適用されます-状態、動作、およびアイデンティティを持つものとして。メッセージを送信してください。
ただし、クラス階層をモデリングツールとして使用する場合もあります。別の例としては、データベースのインスタンスの読み取りと書き込みがあります。データベースI/Oフレームワークは、クラスをデータベーステーブルとペアにすることができます。
例外が提案された方法で実装される場合、例外ハンドラーが存在するすべてのレベルで例外をキャッチして評価する必要があることを意味します。これは、現在の目的と魅力の大部分を打ち負かし、最終的にエラーコードを返し、コールスタックのすべてのレベルでそれらを評価することよりもはるかに優れているでしょう。したがって、OOの観点からは、多分それはクラッジですが、利点はかなりあります。
アニマルキャットの例については、これらの種類の違反は一貫性と実際的な理由のためにしばしば行われます。まだすべての具体的な動物を完全に実装しているわけではないかもしれませんが、たとえばビジターパターンを使用する場合のように、プログラム構造には1つのジェネリッククラスと拡張機能が必要です。
あなたの観察はおそらく、多くの人々が問題ドメインレベルのタイプの問題に対してカスタム例外クラスを作成することに煩わされることができず、この悪い習慣が非常に一般的であるという事実の説明です。
例外の場合に、状態や動作を追加せずにサブタイピングを「許容」するのはなぜですか?私が知っていることから、新しい動作を定義するときはインターフェースを使用し、動作を実装するときや状態を追加するときはクラスを使用する必要があります。
デフォルトのメソッド は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公式ソースであっても、それがであるとは限りません 。それはその時だったかもしれないし、おそらく誰もがこの方法を実装することを考えていなかったかもしれません。
私はそれが悪い習慣だとは思わない。それはデザインと呼ばれています:
記録のためだけに:継承と抽象クラスよりも構成とインターフェースを好みますが、それらには場所と用途があり、Java例外処理は継承プットの例ですサブクラスによって追加の動作や状態が追加されない場合でも、有効に活用できます。