私は(社内で作成されたのではなく、オンラインソースからの)ライブラリを使用して、インターフェイスとその実装を提供しています。
interface FooInterface {
// ...
}
class Foo implements FooInterface {
// ...
}
デフォルトの実装であるFoo
に満足していないので、いつでも次のように書くことができます。
class MyFoo implements FooInterface {
// ...
}
ただし、必要な変更は非常に小さいです。実際、ライブラリの更新バージョンに新しい動作/機能が含まれている場合は、そうしない正当な理由がない限り、これが望まれます。この時点で、2つの選択肢があります。
Foo
全体をMyFoo
に複製し、ライブラリの変更に応じて随時更新します。Foo
をMyFoo
に継承し、MyFoo
で最小限の変更を加えます。ソフトウェアエンジニアリングの観点から、上記の2つのプラクティスのどちらが推奨されますか?
Foo
が拡張方法を文書化していて、Foo
を拡張することで目的を達成できる場合は、それが最適な方法です。ただし、Foo
が継承されることを意図しておらず、作成者がクラスをsealed
/final
にすることを単に怠った可能性があります。その場合、Foo
がライブラリの新しいバージョンでリファクタリングされるとサブクラスが破損する可能性があるため、すべきではありませんFoo
から継承します。
たとえば、Foo.bar
はFoo.baz
の観点から実装される可能性がありますが、これは実装の詳細にすぎません。この仮定の下でサブクラスをコーディングしました。次のリリースでは、bar
はbaz
を呼び出さなくなり、サブクラスが壊れます。逆もまた起こり得ますbar
はbaz
を呼び出さなかったが、今では突然呼び出されます。これは、脆弱な基本クラスの問題として知られています。
言及しなかった3番目のオプションがあります。それは合成を使用することです。FooInterface
の実装はFoo
のインスタンスへの参照を保持し、それを使用してジョブを実行します。 Foo
が拡張されることを意図していない場合、これは可能であれば私がお勧めするルートです。
それでも不可能な場合は、ライブラリの作成者に連絡して、必要な機能を実装できるかどうかを尋ねることを検討してください(または、自分で実装してパッチ/プルリクエストを送信します)。そうすれば、それはライブラリの一部になり、将来のリリースとは異なる独自の実装について心配する必要はありません。
変更するコードの量によって異なります。ほとんどすべてのメソッド実装に満足できない場合は、クラス全体の独自の実装を作成する必要があります。
MyFoo
からFoo
を継承することをお勧めします。あなたがしなければならなかった変更を追跡することができるでしょう。また、インターフェイスを介してコードが使用している可能性のあるデザインパターンを壊すことはありません。Foo
全体をMyFoo
に複製することはお勧めできません。そうすれば、Foo
クラスがメソッドの実装を変更したり、新しい実装を追加したりするたびに、MyFoo
クラスを変更してコードで使用できるようにする必要があります。コードを複製するのではなく、参照してコードを再利用することをお勧めします。