抽象クラス/メソッドは廃止されていますか?


37

以前は、多くの抽象クラス/メソッドを作成していました。その後、インターフェイスの使用を開始しました。

現在、インターフェイスが抽象クラスを廃止していないかどうかはわかりません。

完全に抽象クラスが必要ですか?代わりにインターフェースを作成してください。いくつかの実装を含む抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェイスを実装します。追加の利点は、一部のクラスが親クラスを必要とせず、インターフェイスを実装するだけであることです。

それでは、抽象クラス/メソッドは廃止されていますか?


選択したプログラミング言語がインターフェースをサポートしていない場合はどうですか?私はこれがC ++の場合であることを思い出すようです。
バーナード

7
@Bernard:C ++では、抽象クラス名前以外のすべてのインターフェースです。それらが「純粋な」インターフェース以上のこともできるということは、欠点ではありません。
-gbjbaanb

@gbjbaanb:そうですね。インターフェースとして使用したことを思い出すのではなく、デフォルトの実装を提供するために。
バーナード

インターフェイスは、オブジェクト参照の「通貨」です。一般的に言って、それらは多態的な行動の基礎です。抽象クラスは、deadalnixが完全に説明した別の目的を果たします。
ジギー

4
これは、「現在、交通手段は時代遅れになっているので、車がある」と言っているようなものではありませんか?ええ、ほとんどの場合、あなたは車を使います。しかし、車以外のものが必要かどうかにかかわらず、「交通手段を使用する必要はありません」と言うのは本当に正しくありません。インターフェイスは、実装のない抽象クラスとほとんど同じで、特別な名前が付いていますか?
ジャックV。11年

回答:


111

いや

インターフェイスは、デフォルトの実装、抽象クラス、およびメソッドを提供できません。これは、多くの場合、コードの重複を避けるのに特に役立ちます。

これは、シーケンシャルカップリングを減らすための非常に良い方法でもあります。抽象メソッド/クラスがないと、テンプレートメソッドパターンを実装できません。このウィキペディアの記事をご覧になることをお勧めします:http : //en.wikipedia.org/wiki/Template_method_pattern


3
@deadalnixテンプレートメソッドパターンは非常に危険です。高度に結合されたコードを簡単に作成し、コードのハッキングを開始して、テンプレート化された抽象クラスを拡張して「もう1つのケース」を処理できます。
quant_dev

9
これは、アンチパターンのようです。インターフェイスに対する構成でもまったく同じことができます。同じことが、いくつかの抽象メソッドを持つ部分クラスに適用されます。むしろ、それらをサブクラス化し、オーバーライドするためにクライアントコードを強制的に、あなたは実装がされていなければならない注射し。参照:en.wikipedia.org/wiki/Composition_over_inheritance#Benefits
back2dos

4
これは、シーケンシャルカップリングを減らす方法をまったく説明していません。この目標を達成するにはいくつかの方法が存在することに同意しますが、盲目的に何らかの原理を呼び出す代わりに、あなたが話している問題に答えてください。これが実際の問題に関連する理由を説明できない場合、貨物カルトプログラミングを行うことになります。
deadalnix

3
@deadalnix:テンプレートパターンを使用すると、シーケンシャルカップリングが最適に減少すると言うのカーゴカルトプログラミングです(状態/戦略/工場パターンを使用しても同様に機能する場合があります)。シーケンシャルカップリングは、多くの場合、何かに対する非常にきめ細かい制御を公開した結果であり、トレードオフにすぎません。それでも、合成を使用したシーケンシャルカップリングを持たないラッパーを作成できないという意味ではありません。実際、それは非常に簡単になります。
back2dos

4
Javaには、インターフェースのデフォルト実装があります。それは答えを変えますか?
raptortech97 14年

15

抽象メソッドが存在するため、基本クラス内から呼び出すことができますが、派生クラスで実装できます。基本クラスは次のことを知っています。

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

これは、派生クラスがセットアップおよびクリーンアップアクティビティを認識していなくても、中間パターンにホールを実装するための合理的な方法です。抽象メソッドがなければ、派生クラスを実装DoTaskbase.DoSetup()base.DoCleanup()常に呼び出すことを忘れないでください。

編集

また、実際に名前を知らなくても上記で説明したTemplate Method Patternへのリンクを投稿してくれたdeadalnixに感謝します。:)


2
+ 1、deadalnix 'に対するあなたの答えが好きです。デリゲート(C#)を使用して、より簡単な方法でテンプレートメソッドを実装できることに注意してくださいpublic void DoTask(Action doWork)
。– Joh

1
@Joh-true。ただし、APIによっては必ずしも良いとは限りません。あなたの基本クラスがFruitあり、あなたの派生クラスがそうAppleであるなら、あなたは呼び出したがってmyApple.Eat()myApple.Eat((a) => howToEatApple(a))ます。また、Appleを呼び出す必要はありませんbase.Eat(() => this.howToEatMe())。抽象メソッドをオーバーライドする方がきれいだと思います。
スコットホイットロック

1
@Scott Whitlock:リンゴと果物を使用したあなたの例は、現実から少し切り離されすぎているため、一般的に、継承に行くことが望ましいかどうかを判断できません。明らかに、答えは「依存する」ので、議論の余地がたくさん残っています...とにかく、リファクタリング中にテンプレートメソッドが頻繁に発生することがわかります。たとえば、重複コードを削除するときです。そのような場合、私は通常、型階層をいじりたくないので、継承を避けたいです。この種の手術は、ラムダを使用する方が簡単です。
ヨウ

この質問は、テンプレートメソッドパターンの単純なアプリケーション以上のものを示しています-パブリック/非パブリックアスペクトは、C ++コミュニティでは非仮想インターフェイスパターンとして知られています。パブリックな非仮想関数で仮想関数をラップすることにより(最初は前者が後者を呼び出すだけである場合でも)、後で必要になる可能性のあるセットアップ/クリーンアップ、ログ、プロファイリング、セキュリティチェックなどのカスタマイズのポイントを残します。
トニー

10

いいえ、廃止されていません。

実際、抽象クラス/メソッドとインターフェースの間には、あいまいですが根本的な違いがあります。

これらのいずれかを使用する必要があるクラスのセットに共通の動作(関連クラス、つまり)がある場合は、抽象クラス/メソッドに進みます。

例:クラーク、オフィサー、ディレクター-これらのクラスはすべて、CalculateSalary()が共通であり、抽象基本クラスを使用します。CalculateSalary()は異なる方法で実装できますが、GetAttendance()など、基本クラスで共通の定義を持つものもあります。

クラスの間に共通(選択されたコンテキスト内の無関係なクラス)が何もないが、実装が大きく異なるアクションがある場合は、インターフェイスに進みます。

例:牛、ベンチ、車、テレスコープではなく関連クラスですが、Isortableを使用してそれらを配列に並べ替えることができます。

多相的な観点からアプローチする場合、この違いは通常無視されます。しかし、私は個人的には、上記で説明した理由により、一方が他方よりも適切な状況があると感じています。


6

他の良い答えに加えて、インターフェイスと抽象クラスには基本的な違いがあります。誰も特に言及していません。つまり、インターフェイスは信頼性がはるかに低いため、抽象クラスよりもはるかに大きなテスト負荷を課します。たとえば、次の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のいずれかの可能な実装している人であっても実装無能(悪い)、または積極的に私や私のユーザーへの敵対的(はるかに悪いです)。

抽象クラスを使用すると、これらの問題をすべて回避できます。それらを使用してください!


1
あなたが話している問題は、クラスを開閉原理に違反するポイントに曲げるのではなく、代数データ型を使用して解決する必要があります。
-back2dos

1
第一に、私はそれが良いとか道徳的とか言ったことは一度もありません。あなたはそれを私の口に入れた。第二に(私の実際のポイントである):これは、言語機能が欠けていることの言い訳にすぎません。最後に、抽象クラスのないソリューション:pastebin.com/DxEh8Qfzがあります。それとは対照的に、アプローチはネストされたクラスと抽象クラスを使用し、安全性を必要とするコード以外はすべて泥の大きな塊に投げ込みます。RedFrobberまたはGreenFrobberを強制したい制約に結び付けるべき理由はありません。多くの決定において、利益をもたらすことなく結合とロックを増加させます。
back2dos

1
抽象メソッドは時代遅れであると言うことは間違いなく間違っていますが、一方でインターフェースよりも優先されるべきであると主張することは誤解です。これらは、インターフェイスとは異なる問題を解決するための単なるツールです。
Groo

2
「基本的な違いがあります[...]インターフェイスは、抽象クラスよりも信頼性がはるかに劣ります[...]。あなたのコードで示した違いはアクセス制限に依存していることに同意しませんが、インターフェイスと抽象クラスの間で大幅に異なる理由はないと思います。C#でもそうかもしれませんが、問題は言語に依存しません。
ジョー

1
@ back2dos:Ericのソリューションが実際に代数データ型を使用していることを指摘したいと思います。抽象クラスは合計型です。抽象メソッドの呼び出しは、バリアントのセットに対するパターンマッチングと同じです。
ロドリックチャップマン

4

あなたは自分でそれを言う:

いくつかの実装を含む抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェイスを実装します

「抽象クラスを継承する」と比較すると、多くの作業が必要です。「純粋主義」の視点からコードにアプローチすることで、自分で仕事をすることができますが、実際の利益のためにワークロードに追加しようとせずに、すでに十分なことがあります。


言うまでもなく、クラスがインターフェイスを継承しない場合(言及されていません)、いくつかの言語で転送関数を記述する必要があります。
シェード

うーん、あなたが言及した追加の「たくさんの仕事」は、インターフェースを作成し、クラスにそれを実装させたということです。それに加えて、もちろん、このインターフェースは、抽象基本クラスでは機能しない新しい階層からインターフェースを実装する機能を提供します。
ビルK

4

@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
}

このソリューションは、inheritanceではなく構成を使用するため、より優れています。テンプレートパターンは、モノポリールールの実装とゲームの実行方法の実装との間に依存関係を導入します。ただし、これらはまったく異なる2つの責任であり、それらを結合する正当な理由はありません。


+1。サイドノートとして:「テンプレートパターンがそれらを形式化するという事実にもかかわらず、部分的な実装はアンチパターンです。」ウィキペディアの説明では、パターンを明確に定義しています。コード例のみが「間違っています」(不要な場合に継承を使用し、上記のように単純な代替が存在するという意味で)。言い換えれば、パターン自体が非難されるのではなく、人々がそれを実装する傾向があるだけだと思います。

2

いいえ。提案された代替手段でさえ、抽象クラスの使用を含みます。さらに、言語を指定しなかったので、先に進み、汎用コードはいずれにしても脆弱な継承よりも優れたオプションであると言います。抽象クラスには、インターフェースよりも大きな利点があります。


-1。「一般的なコードと継承」がこの質問にどう関係するのか理解できません。「抽象クラスにはインターフェースよりも大きな利点がある」理由を説明または正当化する必要があります。

1

抽象クラスはインターフェースではありません。それらはインスタンス化できないクラスです。

完全に抽象クラスが必要ですか?代わりにインターフェースを作成してください。いくつかの実装を含む抽象クラスが必要ですか?インターフェイスを作成し、クラスを作成します。クラスを継承し、インターフェイスを実装します。追加の利点は、一部のクラスが親クラスを必要とせず、インターフェイスを実装するだけであることです。

しかし、その後、あなたは非抽象的な役に立たないクラスを持つことになります。基本クラスの機能ホールを埋めるには、抽象メソッドが必要です。

たとえば、このクラスが与えられた場合

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

どちらFrob()も持たないクラスにこの基本機能をどのように実装しIsFrobbingNeededますか?


1
インターフェイスもインスタンス化できません。
シェード

@Sjoerd:ただし、共有実装の基本クラスが必要です。それはインターフェースにはなれません。
コンフィギュレー

1

私は、抽象クラスが重要な役割を果たすサーブレットフレームワークの作成者です。さらに言えば、50%の場合にメソッドをオーバーライドする必要があり、そのメソッドがオーバーライドされなかったというコンパイラーからの警告を表示したい場合、セミ抽象メソッドが必要です。注釈を追加する問題を解決します。質問に戻りますが、抽象クラスとインターフェースには2つの異なるユースケースがあり、これまでのところ誰も時代遅れではありません。


0

インターフェースに時代遅れになるとは思わないが、戦略パターンに時代遅れになるかもしれない。

抽象クラスの主な用途は、実装の一部を延期することです。「クラスの実装のこの部分は異なるものにすることができます」と言います。

残念ながら、抽象クラスは、クライアントにinheritanceを介してこれを行わせます。一方、戦略パターンを使用すると、継承なしで同じ結果を得ることができます。クライアントは常に独自のクラスを定義するのではなく、クラスのインスタンスを作成できます。クラスの「実装」(動作)は動的に変化する可能性があります。戦略パターンには、設計時だけでなく実行時にも動作を変更できるという追加の利点があり、関係するタイプ間のカップリングが大幅に弱くなります。


0

私は一般的に、ABCよりも純粋なインターフェースに関連する保守問題にはるかに多く直面しています。YMMV-知らない、多分私たちのチームはそれらを不適切に使用しただけかもしれません。

そうは言っても、現実世界の類推を使用する場合、機能と状態がまったくない純粋なインターフェースにはどれほどの用途がありますか?USBを例として使用すると、これはかなり安定したインターフェイスです(現在USB 3.2になっていると思いますが、下位互換性も維持しています)。

しかし、それはステートレスなインターフェースではありません。機能が欠けているわけではありません。これは、純粋なインターフェースというよりは抽象基本クラスに似ています。実際には、非常に具体的な機能および状態の要件を持つ具体的なクラスに近く、ポートにプラグインする部分のみが代替可能な部分であるという抽象化のみがあります。

それ以外の場合は、標準化されたフォームファクターとはるかに緩やかな機能要件を備えたコンピューターの「穴」になり、すべてのメーカーがその穴に何かをさせる独自のハードウェアを思い付くまでは何もしません。それははるかに弱い標準になり、「穴」と何をすべきかの仕様に過ぎませんが、それを行う方法の中心的な規定はありません。一方、すべてのハードウェアメーカーが独自の方法で機能と状態をその「穴」にアタッチしようと試みた後、200の異なる方法で終わる可能性があります。

そして、その時点で、他のメーカーとは異なる問題を引き起こす特定のメーカーがいる可能性があります。仕様を更新する必要がある場合は、200種類の具体的なUSBポートの実装があり、まったく異なる方法で仕様に取り組み、更新とテストを行う必要があります。一部のメーカーは、すべてではなく、自社間で共有する事実上の標準実装(そのインターフェイスを実装するアナロジーの基本クラス)を開発する場合があります。一部のバージョンは他のバージョンより遅い場合があります。スループットは向上するものの、遅延が低下するものもあれば、その逆もあります。他のものより多くのバッテリー電力を使用するかもしれません。USBポートで動作するはずのすべてのハードウェアで動作せずに動作しないものもあります。いくつかは、原子炉を作動させるために取り付ける必要があり、それはユーザーに放射線中毒を与える傾向があります。

そして、それは私が個人的に純粋なインターフェースで見つけたものです。CPUケースに対してマザーボードのフォームファクターをモデル化するだけのように、それらが理にかなっている場合があります。実際、フォームファクターのアナロジーは、ステートレスであり、アナロジー的な「穴」と同様に機能性を欠いています。しかし、チームがすべての場合において何らかの理由で優れていると考えることは、大きな違いであることをよく考えます。

それとは反対に、ABCの方がインターフェースよりもはるかに多くのケースを解決できると思います。インターフェースが2つの選択肢である場合は、チームが非常に大きく、1つの中央標準ではなく200維持します。私が所属していた元チームでは、主に上記のメンテナンスの問題に対応するため、ABCと多重継承を許可するためにコーディング標準を緩めるためだけに実際に懸命に戦わなければなりませんでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.