私は以前、多くの抽象クラス/メソッドを作成していました。それからインターフェースを使い始めました。
今では、インターフェイスが抽象クラスを時代遅れにしていないかどうかはわかりません。
完全に抽象的なクラスが必要ですか?代わりにインターフェースを作成します。あなたはそれにいくつかの実装を持つ抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェースを実装します。追加の利点は、一部のクラスは親クラスを必要とせず、インターフェースを実装するだけであることです。
それで、抽象クラス/メソッドは時代遅れですか?
番号。
インターフェースはデフォルトの実装を提供できず、抽象クラスとメソッドは提供できます。これは、多くの場合、コードの重複を回避するのに特に役立ちます。
これは、シーケンシャルカップリングを削減するための本当に良い方法でもあります。抽象メソッド/クラスがないと、テンプレートメソッドパターンを実装できません。このウィキペディアの記事をご覧になることをお勧めします: http://en.wikipedia.org/wiki/Template_method_pattern
抽象メソッドが存在するため、基本クラス内から呼び出すことができますが、派生クラスに実装できます。だからあなたの基本クラスは知っています:
_public void DoTask()
{
doSetup();
DoWork();
doCleanup();
}
protected abstract void DoWork();
_
これは、派生クラスがセットアップとクリーンアップのアクティビティについて知らなくても、中間パターンに穴を実装するのに適した方法です。抽象メソッドがなければ、DoTask
を実装する派生クラスに依存し、常にbase.DoSetup()
およびbase.DoCleanup()
を呼び出すことを忘れないでください。
編集
また、実際に名前を知らずに上記で説明した テンプレートメソッドパターン へのリンクを投稿してくれたdeadalnixに感謝します。 :)
いいえ、古いものではありません。
実際、抽象クラス/メソッドとインターフェースの間には、あいまいですが根本的な違いがあります。
これらのいずれかを使用する必要があるクラスのセットが、それらが共有する共通の動作(関連クラス、つまり)を持っている場合は、抽象クラス/メソッドに進みます。
例:事務員、オフィサー、ディレクター-これらのクラスはすべて共通のCalculateSalary()を持ち、抽象基本クラスを使用します。
クラスの間に共通のものは何もない(選択されたコンテキストでは関連のないクラス)が、実装が大きく異なるアクションがある場合は、インターフェイスに移動します。
例:牛、ベンチ、車、望遠鏡関連クラスではありませんが、Isortableを使用してそれらを配列にソートできます。
多相性の観点からアプローチすると、この違いは通常無視されます。しかし、個人的には、上記の理由から、一方が他方よりも適切であるという状況があると感じています。
他の良い答えに加えて、インターフェイスと抽象クラスの間には、誰も具体的に述べていない根本的な違いがあります。つまり、インターフェイスははるかに少ないreliableしたがって、抽象クラスよりもはるかに大きなテスト負荷を課します。たとえば、次のC#コードを考えてみます。
public abstract class Frobber
{
private Frobber() {}
public abstract void Frob(Frotz frotz);
private class GreenFrobber : Frobber
{ ... }
private class RedFrobber : Frobber
{ ... }
public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}
public sealed class Frotz
{
public void Frobbit(Frobber frobber)
{
...
frobber.Frob(this);
...
}
}
テストする必要があるコードパスは2つしかないことが保証されています。 Frobbitの作者は、フロバーが赤か緑のどちらかであるという事実に頼ることができます。
代わりに私たちが言う場合:
public interface IFrobber
{
void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }
public sealed class Frotz
{
public void Frobbit(IFrobber frobber)
{
...
frobber.Frob(this);
...
}
}
私は今、Frobへの呼び出しの影響について、まったく何も知らない。 FrobbitのすべてのコードがIFrobberの可能な実装に対して堅牢であることを確認する必要があります。能力がない人による実装(悪い)または私または私のユーザーにアクティブに敵対的(はるかに悪い)。
抽象クラスを使用すると、これらすべての問題を回避できます。それらを使用してください!
@deadnixの投稿でコメントしたように:テンプレートパターンがそれらを形式化しているという事実にもかかわらず、部分的な実装はアンチパターンです。
これに対するクリーンなソリューション テンプレートパターンのウィキペディアの例 :
interface Game {
void initialize(int playersCount);
void makePlay(int player);
boolean done();
void finished();
void printWinner();
}
class GameRunner {
public void playOneGame(int playersCount, Game game) {
game.initialize(playersCount);
int j = 0;
for (int i = 0; !game.finished(); i++)
game.makePlay(i % playersCount);
game.printWinner();
}
}
class Monopoly implements Game {
//... implementation
}
継承の代わりに構成 を使用するため、このソリューションの方が優れています。テンプレートパターンは、モノポリールールの実装とゲームの実行方法の実装との間に依存関係をもたらします。ただし、これらは2つのまったく異なる責任であり、それらを結合する正当な理由はありません。
あなたはそれを自分で言う:
あなたはそれにいくつかの実装を持つ抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェースを実装する
「抽象クラスを継承する」と比較すると、多くの作業に聞こえます。 「純粋」な視点からコードにアプローチすることで自分で作業を行うことができますが、実際的なメリットがないために、ワークロードに追加することを試みなくても、十分なことはすでにあります。
いいえ。提案された代替案にも抽象クラスの使用が含まれます。さらに、言語を指定しなかったので、先に進んで、とにかく脆弱な継承よりも汎用コードの方が優れたオプションであると言います。抽象クラスには、インターフェイスよりも大きな利点があります。
私は、抽象クラスが重要な役割を果たすサーブレットフレームワークの作成者です。さらに言えば、50%のケースでメソッドをオーバーライドする必要があり、そのメソッドがオーバーライドされなかったというコンパイラからの警告を表示したい場合は、半抽象メソッドが必要です。注釈を追加する問題を解決します。質問に戻ると、抽象クラスとインターフェースの2つの異なるユースケースがあり、今のところ時代遅れになっている人はいません。
抽象クラスはインターフェースではありません。これらはインスタンス化できないクラスです。
完全に抽象クラスが必要ですか?代わりにインターフェースを作成します。あなたはそれにいくつかの実装を持つ抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェースを実装します。追加の利点は、一部のクラスは親クラスを必要とせず、インターフェースを実装するだけであることです。
しかし、そうすると、抽象的なものではない役に立たないクラスができてしまいます。基本クラスの機能の穴を埋めるには、抽象メソッドが必要です。
たとえば、このクラスを考えると
_public abstract class Frobber {
public abstract void Frob();
public abstract boolean IsFrobbingNeeded { get; }
public void FrobUntilFinished() {
while (IsFrobbingNeeded) {
Frob();
}
}
}
_
Frob()
もIsFrobbingNeeded
も持たないクラスに、この基本機能をどのように実装しますか?
インターフェースによって廃止されたとは思わないが、戦略パターンによって廃止された可能性がある。
抽象クラスの主な用途は、実装の一部を延期することです。つまり、「クラスの実装のこの部分は異なるものにすることができます」。
残念ながら、抽象クラスはinheritanceを介してクライアントにこれを強制します。一方、戦略パターンでは、継承なしで同じ結果を達成できます。クライアントは常にインスタンスを定義するのではなく、クラスのインスタンスを作成でき、クラスの「実装」(動作)は動的に変化する可能性があります。戦略パターンには、設計時だけでなく実行時に動作を変更できるという追加の利点があり、関係する型間の結合が大幅に弱くなっています。
私は一般に、ABCよりも純粋なインターフェースに関連する保守問題にはるかに直面してきました。 YMMV-知らない、おそらく私たちのチームはそれらを不適切に使用しただけかもしれません。
とはいえ、実際の類推を使用する場合、機能と状態をまったく持たない純粋なインターフェイスはどの程度使用されますか?私が例としてUSBを使用する場合、それはかなり安定したインターフェースです(私たちは現在USB 3.2にいると思いますが、下位互換性も維持しています)。
しかし、それはステートレスなインターフェースではありません。機能性が欠けているわけではありません。純粋なインターフェースというよりは、抽象基本クラスのようなものです。実際には、非常に具体的な機能と状態の要件を持つ具象クラスに近く、唯一の抽象化は、ポートに接続するものが唯一の置換可能な部分であることです。
それ以外の場合は、標準化されたフォームファクタとはるかに緩い機能要件を持つコンピュータの「穴」に過ぎず、すべての製造元が独自のハードウェアを考案してその穴に何かをさせるまで、それ自体は何もしません。それははるかに弱い標準になり、「穴」とそれが何をすべきかについての仕様に過ぎませんが、それを行う方法についての中心的な規定はありません。その間、すべてのハードウェアメーカーが機能と状態をその「穴」に接続するための独自の方法を考え出そうとすると、最終的には200の異なる方法になる可能性があります。
その時点で、特定の製造元が他の製造元とは異なる問題を引き起こす可能性があります。仕様を更新する必要がある場合、200の異なる具体的なUSBポートの実装があり、仕様への取り組み方がまったく異なり、更新およびテストが必要になる可能性があります。一部の製造元は、自社間で共有する事実上の標準実装(そのインターフェースを実装する類似の基本クラス)を開発する場合がありますが、すべてではありません。一部のバージョンは他のバージョンより遅い場合があります。スループットが優れていても、レイテンシが悪い場合や、その逆の場合があります。一部のユーザーは他のユーザーよりも多くのバッテリー電力を使用する場合があります。 USBポートで動作するはずのすべてのハードウェアで動作しない場合があります。原子炉を運転するために取り付ける必要があり、そのためにユーザーに放射線中毒を起こす傾向があります。
そして、それは私が個人的に純粋なインターフェースで見つけたものです。 CPUケースに対してマザーボードのフォームファクターをモデル化する場合など、それらが意味をなす場合があります。実際、フォームファクターのアナロジーは、アナロジーの「ホール」と同様に、ほとんどステートレスで機能性がありません。しかし、チームがそれをすべてのケースで優れていると見なすことは、近いものではなく、チームにとって大きな間違いであると私はよく考えます。
それどころか、ABCがインターフェースよりもはるかに多くのケースを解決できるのは、インターフェースが2つの選択肢である場合です。維持する。私が所属していた以前のチームでは、ABCと多重継承を可能にするためにコーディング標準を緩めるために、そして主に上記のメンテナンスの問題に対応するために、実際に懸命に戦わなければなりませんでした。