web-dev-qa-db-ja.com

既存のクラスの変更:新しいクラスに拡張する必要がありますか?

私は(社内で作成されたのではなく、オンラインソースからの)ライブラリを使用して、インターフェイスとその実装を提供しています。

interface FooInterface {
   // ...
}

class Foo implements FooInterface {
  // ...
}

デフォルトの実装であるFooに満足していないので、いつでも次のように書くことができます。

class MyFoo implements FooInterface {
  // ...
}

ただし、必要な変更は非常に小さいです。実際、ライブラリの更新バージョンに新しい動作/機能が含まれている場合は、そうしない正当な理由がない限り、これが望まれます。この時点で、2つの選択肢があります。

  • 変更された部分を除いて、Foo全体をMyFooに複製し、ライブラリの変更に応じて随時更新します。
  • FooMyFooに継承し、MyFooで最小限の変更を加えます。

ソフトウェアエンジニアリングの観点から、上記の2つのプラクティスのどちらが推奨されますか?

2
user2064000

Fooが拡張方法を文書化していて、Fooを拡張することで目的を達成できる場合は、それが最適な方法です。ただし、Fooが継承されることを意図しておらず、作成者がクラスをsealed/finalにすることを単に怠った可能性があります。その場合、Fooがライブラリの新しいバージョンでリファクタリングされるとサブクラスが破損する可能性があるため、すべきではありませんFooから継承します。

たとえば、Foo.barFoo.bazの観点から実装される可能性がありますが、これは実装の詳細にすぎません。この仮定の下でサブクラスをコーディングしました。次のリリースでは、barbazを呼び出さなくなり、サブクラスが壊れます。逆もまた起こり得ますbarbazを呼び出さなかったが、今では突然呼び出されます。これは、脆弱な基本クラスの問題として知られています。

言及しなかった3番目のオプションがあります。それは合成を使用することです。FooInterfaceの実装はFooのインスタンスへの参照を保持し、それを使用してジョブを実行します。 Fooが拡張されることを意図していない場合、これは可能であれば私がお勧めするルートです。

それでも不可能な場合は、ライブラリの作成者に連絡して、必要な機能を実装できるかどうかを尋ねることを検討してください(または、自分で実装してパッチ/プルリクエストを送信します)。そうすれば、それはライブラリの一部になり、将来のリリースとは異なる独自の実装について心配する必要はありません。

4
Doval

変更するコードの量によって異なります。ほとんどすべてのメソッド実装に満足できない場合は、クラス全体の独自の実装を作成する必要があります。

  • ただし、小さな変更の場合は、MyFooからFooを継承することをお勧めします。あなたがしなければならなかった変更を追跡することができるでしょう。また、インターフェイスを介してコードが使用している可能性のあるデザインパターンを壊すことはありません。
  • Foo全体をMyFooに複製することはお勧めできません。そうすれば、Fooクラスがメソッドの実装を変更したり、新しい実装を追加したりするたびに、MyFooクラスを変更してコードで使用できるようにする必要があります。コードを複製するのではなく、参照してコードを再利用することをお勧めします。
1
happydevdays