web-dev-qa-db-ja.com

抽象クラスのインターフェース

同僚と私は、基本クラスとインターフェースの関係について異なる意見を持っています。インターフェースの実装が必要なときにそのクラスを使用できない限り、クラスはインターフェースを実装すべきではないと私は信じています。言い換えれば、私はこのようなコードを見たいです:

interface IFooWorker { void Work(); }

abstract class BaseWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker, IFooWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

DbWorkerは、インターフェイスのインスタンス化可能な実装であるため、IFooWorkerインターフェイスを取得します。それは完全に契約を満たします。私の同僚はほぼ同じものを好みます:

interface IFooWorker { void Work(); }

abstract class BaseWorker : IFooWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

基本クラスがインターフェースを取得する場所。これにより、基本クラスのすべての継承者もそのインターフェースを継承します。これは私を悩ませますが、「基本クラスがインターフェースの実装としてそれ自体で立つことができない」以外に、具体的な理由を思い付くことはできません。

彼の方法と私の方法の長所と短所は何ですか?なぜ、ある方法を別の方法で使用する必要があるのですか?

31
Bryan Boettcher

インターフェースの実装が必要なときにそのクラスを使用できない限り、クラスはインターフェースを実装すべきではないと私は信じています。

BaseWorkerはその要件を満たしています。 BaseWorkerオブジェクトを直接インスタンス化できないからといって、コントラクトを満たすBaseWorkerpointerがないことを意味するわけではありません。実際、それが抽象クラスの要点のほとんどです。

また、投稿した単純化した例から見分けるのは難しいですが、問題の一部として、インターフェースと抽象クラスが重複している可能性があります。 notを実装するIFooWorkerを実装する他のクラスがBaseWorkerから派生していない限り、インターフェイスはまったく必要ありません。インターフェースは、多重継承を容易にするいくつかの追加の制限がある単なる抽象クラスです。

ここでも、単純化された例からはわかりにくいですが、プロテクトメソッドの使用、派生クラスのベースを明示的に参照していること、およびインターフェース実装を宣言する明確な場所がないことは、代わりに継承を不適切に使用していることを警告する兆候です組成。その継承がなければ、あなたの質問全体が意味をなさなくなります。

24
Karl Bielefeldt

私はあなたの同僚に同意する必要があります。

どちらの例でも、BaseWorkerは抽象メソッドWork()を定義します。これは、すべてのサブクラスがIFooWorkerのコントラクトに対応できることを意味します。この場合、BaseWorkerはインターフェースを実装する必要があり、その実装はそのサブクラスによって継承されます。これにより、各サブクラスが実際にIFooWorker(DRY原則)であることを明示的に示す必要がなくなります。

Work()をBaseWorkerのメソッドとして定義していない場合、またはIFooWorkerにBaseWorkerのサブクラスが必要としない、または必要としない他のメソッドがある場合、(明らかに)実際にどのサブクラスがIFooWorkerを実装するかを示す必要があります。

47
Matthew Flynn

私は一般的にあなたの同僚に同意します。

モデルを見てみましょう。基本クラスもIFooWorkerメソッドの公開を強制しているにもかかわらず、インターフェイスは子クラスによってのみ実装されます。まず、冗長です。子クラスがインターフェイスを実装するかどうかに関係なく、BaseWorkerの公開されたメソッドをオーバーライドする必要があります。同様に、IFooWorker実装は、BaseWorkerからヘルプを取得するかどうかにかかわらず、機能を公開する必要があります。

これにより、階層が曖昧になります。 「すべてのIFooWorkersはBaseWorkersです」は必ずしも正しい説明ではなく、「すべてのBaseooersはIFooWorkersでもない」というわけではありません。したがって、IFooWorkerまたはBaseWorker(最初に継承する理由の1つである共通の公開された機能を利用する)の実装であるインスタンス変数、パラメーター、または戻り値の型を定義する場合は、これらはすべてを網羅していることが保証されています。一部のBaseWorkersは、IFooWorker型の変数に割り当てることができません。逆も同様です。

同僚のモデルは、使いやすく、置き換えが簡単です。 「すべてのBaseWorkersはIFooWorkersです」は今や真実です。任意のBaseWorkerインスタンスを任意のIFooWorker依存関係に与えることができ、問題はありません。反対のステートメント「すべてのIFooWorkersはBaseWorkersです」は当てはまりません。これにより、BaseWorkerをBetterBaseWorkerに置き換えることができ、使用するコード(IFooWorkerに依存)が違いを通知する必要がなくなります。

11
KeithS

これらの回答に警告を追加する必要があります。基本クラスをインターフェースに結合すると、そのクラスの構造に力が生じます。基本的な例では、2つを結合する必要はありませんが、すべての場合に当てはまるわけではありません。

Javaのコレクションフレームワーククラスを取り上げます。

abstract class AbstractList
class LinkedList extends AbstractList implements Queue

QueueコントラクトがLinkedListによって実装されるという事実は、懸念をAbstractListにプッシュしませんでした。

2つのケースの違いは何ですか? BaseWorkerの目的は、常にIWorkerで操作を実装することでした(その名前とインターフェースで伝えられたとおり)。 AbstractListの目的とQueueの目的は異なりますが、前者の子孫でも後者のコントラクトを実装できます。

6
Mihai Danila

新しいメソッドの追加など、IFooWorkerを変更するとどうなりますか?

BaseWorkerがインターフェースを実装する場合、抽象であっても、デフォルトで新しいメソッドを宣言する必要があります。一方、インターフェイスを実装していない場合は、派生クラスでのみコンパイルエラーが発生します。

そのため、基本クラスにインターフェイスを実装させるです。これは、mightがすべての機能を実装できるためです派生クラスに触れることなく、基本クラスの新しいメソッドのために。

2
Scott Whitlock

抽象クラスとインターフェースの違いを完全に理解するまでにはまだ数年かかります。基本的な概念を理解できると思うたびに、私はstackexchangeを探しに行き、2ステップ戻ります。しかし、トピックとOPの質問に関するいくつかの考え:

最初:

インターフェースには2つの一般的な説明があります。

  1. インターフェースは、任意のクラスが実装できるメソッドとプロパティのリストであり、インターフェースを実装することにより、クラスはそれらのメソッド(およびそのシグネチャ)とそれらのプロパティ(およびそれらのタイプ)がそのクラスと「インターフェース」するときに利用できることを保証します。そのクラスのオブジェクト。インターフェースは契約です。

  2. インターフェースは、何もしない/できない抽象的なクラスです。これらは、親クラスとは異なり、複数を実装できるので便利です。同様に、私はBananaBreadクラスのオブジェクトになり、BaseBreadから継承することができますが、これは、IWithNutsとITastesYummyの両方のインターフェイスを実装できないことを意味しません。 IDoesTheDishesインターフェイスを実装することもできます。私は単なるパンではないからですよね。

抽象クラスには、2つの一般的な説明があります。

  1. 抽象クラスは、そうですthingは、物事にできることではありません。それは本質のようなもので、本当のことではありません。ちょっと待ってください、これが役立ちます。ボートは抽象クラスですが、セクシーなプレイボーイヨットはBaseBoatのサブクラスになります。

  2. 私は抽象クラスの本を読みましたが、おそらくあなたはその本を読んだ方がよいでしょう。おそらくその本を読んでいないと、おそらくそれを理解できず、間違って読むでしょう。

ちなみに、私が混乱して立ち去ったとしても、シターズの本はいつも印象的です。

第二:

SOでは、誰かがこの質問のより単純なバージョンであるクラシックな質問をしました。そして、一つの答えは、空軍パイロットを簡単な例として使用しました。それは着陸しませんでしたが、いくつかの素晴らしいコメントを引き起こしました。そのうちの1つは、takeOff、pilotEjectなどのメソッドを持つIFlyableインターフェースについて言及しました。そして、インターフェースが単に有用でない理由の実例として、これは本当に私にクリックしました。しかし重要です。インターフェースはオブジェクト/クラスを直感的にするか、少なくともそれがそうであるという感覚を与えます。インターフェースはオブジェクトやデータのためではなく、そのオブジェクトと対話する必要があるもののためのものです。古典的な果物->アップル->富士または形状->三角形->等辺継承の例は、その子孫に基づいて特定のオブジェクトを分類学的に理解するための優れたモデルです。一般的な品質、動作、オブジェクトが何かのグループであるかどうか、システムを間違った場所に置いた場合にシステムにホースを送るか、感覚データ、特定のデータストアへのコネクタについて説明するか、または消費者とプロセッサに通知します。給与計算に必要な財務データ。

特定のPlaneオブジェクトには緊急着陸のメソッドがある場合とない場合がありますが、他のすべてのフライアブルオブジェクトと同様に、DumbPlaneでカバーされているウェイクアップし、開発者がquickLand彼らはそれがすべて同じだと思ったからです。すべてのねじメーカーがきちんとタイトに独自の解釈を持っていた場合、または私のテレビに音量減少ボタンの上に音量増加ボタンがない場合、私がいらいらするのと同じように。

抽象クラスは、子孫オブジェクトがそのクラスとして認定される必要があるものを確立するモデルです。アヒルのすべての品質を備えていない場合は、IQuackインターフェースを実装していることは問題ではなく、ただの奇妙なペンギンです。インターフェースは、他に確信が持てない場合でも意味のあるものです。 Jeff GoldblumとStarbuckは、インターフェイスが確実に類似していたため、どちらもエイリアンの宇宙船を飛行させることができました。

第三:

最初から特定の方法を強制する必要がある場合があるため、同僚にも同意します。 Active Record ORMを作成している場合は、saveメソッドが必要です。これは、インスタンス化できるサブクラス次第ではありません。 ICRUDインターフェースが1つの抽象クラスに排他的に結合されないほど移植可能である場合、それを他のクラスで実装して、その抽象クラスの子孫クラスのいずれかを既に理解している人にとって信頼性が高く直感的なものにすることができます。

一方、リストのすべての型がキューインターフェイスを実装する(または実装する必要がある)わけではないため、抽象クラスへのインターフェイスの関連付けにジャンプしない場合の良い例が以前にありました。あなたはこのシナリオが半分の時間で起こると言いました、それはあなたとあなたの同僚は両方とも半分の時間で間違っていることを意味します、そしてそれ故に行うべき最善のことは議論、議論、検討し、そして彼らが正しいと判明したらカップリングを認めて受け入れることです。しかし、それが現在の仕事に最適ではない場合でも、哲学に従う開発者にならないでください。

1
Anthony

まず、抽象基本クラスとは何か、インターフェイスとは何かについて考えます。どちらを使用するか、いつ使用しないかを検討してください。

人々が両方とも非常に類似した概念であると考えることは一般的です。実際、それは一般的なインタビューの質問です(2つの違いは??)。

したがって、抽象基本クラスは、メソッドのデフォルトの実装であるインターフェースでは実現できないものを提供します。 (たとえば、C#では、インターフェイスではなく、抽象クラスに静的メソッドを設定できます)。

例として、これの一般的な用途の1つはIDisposableです。抽象クラスはIDisposableのDisposeメソッドを実装します。つまり、基本クラスの派生バージョンはすべて自動的に破棄されます。その後、いくつかのオプションで遊ぶことができます。 Disposeを抽象化し、派生クラスに強制的に実装させます。仮想デフォルト実装を提供して、提供しないか、仮想または抽象化せずに、BeforeDispose、AfterDispose、OnDispose、Disposingなどの仮想メソッドを呼び出すようにします。

したがって、すべての派生クラスがインターフェースをサポートする必要があるときはいつでも、それは基本クラスになります。 1つまたはいくつかだけがそのインターフェイスを必要とする場合、派生クラスになります。

それは実際には単純化よりも総体的なものです。もう1つの方法は、派生クラスにインターフェースを実装することさえせずに、アダプターパターンを介して提供することです。私が最近見たこの例は、IObjectContextAdapterにありました。

1
Ian

あなたが示唆するように、なぜDbWorkerIFooWorkerを実装すべきなのかには別の側面があります。

同僚のスタイルの場合、何らかの理由で後でリファクタリングが発生し、DbWorkerが抽象BaseWorkerクラスを拡張しないと見なされた場合、DbWorkerIFooWorker インターフェース。 IFooWorkerインターフェースを期待している場合、これは、それを消費するクライアントに影響を与える可能性がありますが、必ずしもそうとは限りません。

0