シングルトン、抽象クラス、インターフェースの役割は何ですか?


13

私はC ++でOOPを勉強しており、これら3つの概念の定義を知っていても、それをいつどのように使用するかを本当に理解できません。

このクラスを例に使用してみましょう。

class Person{
    private:
             string name;
             int age;
    public:
             Person(string p1, int p2){this->name=p1; this->age=p2;}
             ~Person(){}

             void set_name (string parameter){this->name=parameter;}                 
             void set_age (int parameter){this->age=parameter;}

             string get_name (){return this->name;}
             int get_age (){return this->age;}

             };

1. シングルトン

クラスが1つのオブジェクトのみを機能させるという制限はどのように機能しますか?

CANあなたは持っているだろう、クラス設計ONLY 2のインスタンスを?それとも3?

WHEN /必要な推奨シングルトンを使用していますか?それは良い習慣ですか?

2. 抽象クラス

私の知る限り、純粋な仮想関数が1つしかない場合、クラスは抽象になります。だから、追加

virtual void print ()=0;

しますよね?

なぜあなたは、そのオブジェクト必要とされていないクラスが必要でしょうか?

3.インターフェース

インターフェイスがすべてのメソッドが純粋な仮想関数である抽象クラスである場合、

WHATそれらの2の主な違いは何ですか?

前もって感謝します!


2
シングルトンは議論の余地があります。さまざまな意見を得るには、このサイトで検索してください。
ウィンストンイーバート

2
抽象クラスは言語の一部ですが、シングルトンもインターフェイスもそうではないことにも注意してください。それらは人々が実装するパターンです。特にシングルトンは、動作させるために少し巧妙なハッキングを必要とするものです。(もちろん、慣例によってのみシングルトンを作成できます。)
ロボットを手に

1
一度に1つずつお願いします。
-JeffO

回答:


17

1.シングルトン

コンストラクターはプライベートであるため、インスタンスの数を制限します。つまり、静的メソッドのみがそのクラスのインスタンスを作成できます(実際にそれを達成するための他の汚いトリックがありますが、夢中になりません)。

2つまたは3つのインスタンスのみを持つクラスを作成することは完全に実行可能です。システム全体でそのクラスのインスタンスを1つだけにする必要があると感じるときはいつでも、シングルトンを使用する必要があります。これは通常、「マネージャー」動作を持つクラスで発生します。

シングルトンについて詳しく知りたい場合は、この投稿ウィキペディア、特にC ++を参照してください

このパターンには良い点と悪い点がいくつかありますが、この議論は別の場所に属します。

2.抽象クラス

はい、そうです。単一の仮想メソッドのみがクラスを抽象としてマークします。

上位クラスを実際にインスタンス化する必要のない、より大きなクラス階層がある場合は、これらの種類のクラスを使用します。

哺乳類のクラスを定義し、それをDogとCatに継承すると仮定しましょう。あなたがそれについて考えるならば、あなたが最初にそれが本当にどんな哺乳類であるかを知る必要があるので、哺乳類の純粋なインスタンスを持っていることはあまり意味がありません。

潜在的には、継承されたクラスでのみ意味をなすMakeSound()というメソッドがありますが、すべての哺乳類が作成できる共通の音はありません(ここでは、哺乳類の音を主張しようとしない例です)。

つまり、哺乳動物はすべての哺乳動物に共通の動作を実装するが、実際にはインスタンス化されることを想定していないため、哺乳動物は抽象クラスであるべきだということです。それが抽象クラスの背後にある基本概念ですが、学ぶべきことは間違いなくあります。

3.インターフェース

JavaやC#にあるのと同じ意味で、C ++には純粋なインターフェースはありません。インターフェースを作成する唯一の方法は、インターフェースに必要な動作のほとんどを模倣する純粋な抽象クラスを持つことです。

基本的に、探している動作は、基本となる実装を気にせずに他のオブジェクトが対話できるコントラクトを定義することです。クラスを純粋に抽象化すると、すべての実装が別の場所に属するため、そのクラスの目的は、定義するコントラクトのみになります。これはオブジェクト指向の非常に強力な概念であり、あなたは間違いなくそれをもっと調べるべきです。

より良いアイデアを得るために、MSDNでC#のインターフェイス仕様について読むことができます。

http://msdn.microsoft.com/en-us/library/ms173156.aspx

C ++は、純粋な抽象クラスを持つことで、同じ種類の動作を提供します。


2
純粋な抽象基本クラスは、インターフェースが行うすべてを提供します。Java(およびC#)にはインターフェイスが存在します。これは、言語設計者が複数の継承を防止したい(頭痛のせいで)が、問題のない多重継承の非常に一般的な使用法を認識したためです。
ロボット

@StevenBurnap:しかし、質問の文脈であるC ++ではそうではありません。
DeadMG

3
彼はC ++とインターフェースについて尋ねています。「インターフェース」はC ++の言語機能ではありませんが、人々は確かに、抽象基底クラスを使用してJavaインターフェースとまったく同じように動作するインターフェースをC ++で作成します。彼らはJavaが存在する前にそうしました。
ロボット

accu.org/index.php/journals/233を参照してください。
ロボット

1
同じことがシングルトンにも当てはまります。C ++では、どちらもデザインパターンであり、言語機能ではありません。これは、人々がC ++のインターフェースとその目的について話さないという意味ではありません。「インターフェース」の概念は、純粋なCで使用するために元々開発されたCorbaやCOMなどのコンポーネントシステムから生まれました。C++では、インターフェースは通常、すべてのメソッドが仮想である抽象基本クラスで実装されます。これの機能は、Javaインターフェースの機能と同じです。そのため、Javaインターフェースの概念は、意図的にC ++抽象クラスのサブセットです。
ロボット

8

ほとんどの人は、シングルトン/抽象クラスが何であるかをすでに説明しています。うまくいけば、少し異なる視点を提供し、いくつかの実用的な例を示します。

シングルトン-すべての呼び出しコードで変数の単一インスタンスを使用する場合、何らかの理由で、次のオプションがあります。

  • グローバル変数-明らかにカプセル化なし、グローバルに結合されたコードのほとんど...悪い
  • すべての静的関数を含むクラス-単純なグローバルよりも少し優れていますが、この設計上の決定は、コードがグローバルデータに依存し、後で変更するのが非常に困難な場合があるパスへと導きます。また、静的関数だけを使用している場合は、ポリモーフィズムなどのオブジェクト指向オブジェクトを利用できません
  • シングルトン-クラスのインスタンスは1つしかありませんが、クラスの実際の実装はグローバルであるという事実について何も知る必要はありません。したがって、今日はシングルトンのクラスを作成できます。明日は、コンストラクターを単純に公開し、クライアントに複数のコピーをインスタンス化させることができます。シングルトンを参照するほとんどのクライアントコードは変更する必要がなく、シングルトン自体の実装を変更する必要はありません。唯一の変更点は、クライアントコードが最初にシングルトン参照を取得する方法です。

世の中にある悪と悪のオプションの中で、グローバルデータが必要な場合、シングルトンは前の2つよりもはるかに優れたアプローチです。また、明日あなたが気分を変えて、グローバルデータの代わりに制御の反転を使用することに決めた場合、オプションを開いたままにできます。

それでは、シングルトンはどこで使用しますか?次に例を示します。

  • ロギング-プロセス全体に単一のログを持たせたい場合は、ログオブジェクトを作成してどこにでも渡すことができます。しかし、100,000k行のレガシーアプリケーションコードがある場合はどうでしょうか。それらをすべて変更しますか?または、以下を簡単に紹介し、好きな場所で使用を開始できます。

    CLog::GetInstance().write( "my log message goes here" );
  • サーバー接続キャッシュ-これは、アプリケーションで紹介しなければならなかったことです。私たちのコードベースは、それが好きなときにサーバーに接続するために使用されていました。ネットワークに何らかの遅延がない限り、ほとんどの場合これで問題ありませんでした。ソリューションが必要でしたが、10年前のアプリケーションの再設計は実際には行われていませんでした。シングルトンCServerConnectionManagerを作成しました。次に、コードを検索し、CoCreateInstanceWithAuth呼び出しを、クラスを呼び出した同一の署名呼び出しに置き換えました。現在、最初の接続試行がキャッシュされ、残りの時間の「接続」試行は瞬時に行われました。シングルトンは悪だと言う人もいます。彼らは私の尻を救ったと言います。

  • デバッグには、グローバルな実行中のオブジェクトテーブルが非常に役立つことがよくあります。追跡したいクラスがいくつかあります。それらはすべて同じ基本クラスから派生しています。インスタンス化中に、オブジェクトテーブルシングルトンを呼び出し、自分自身を登録します。破棄されると、登録解除されます。任意のマシンに移動して、プロセスにアタッチし、実行中のオブジェクトのリストを作成できます。半年以上製品に携わっており、2つの「グローバル」オブジェクトテーブルが必要だとは思っていませんでした。

  • 正規表現に依存する比較的複雑な文字列パーサーユーティリティクラスがいくつかあります。正規表現クラスは、一致を実行する前に初期化する必要があります。初期化は、解析文字列に基づいてFSMが生成されるため、やや高価です。ただし、一度構築されたFSMは変更されないため、その後、正規表現クラスは100スレッドで安全にアクセスできます。これらのパーサークラスは、シングルトンを内部的に使用して、この初期化が1回だけ行われるようにします。これにより、パフォーマンスが大幅に向上し、「邪悪なシングルトン」による問題が発生することはありませんでした。

これをすべて言ったが、いつ、どこでシングルトンを使用するかを覚えておく必要がある。10回のうち9回はより良い解決策がありますので、代わりにそれを使用する必要があります。ただし、シングルトンが絶対に正しい設計選択である場合があります。

次のトピック...インターフェイスと抽象クラス。最初に他の人が述べたように、インターフェースは抽象クラスですが、実装を絶対に持たないことを強制することでそれを超えています。一部の言語では、インターフェイスキーワードは言語の一部です。C ++では、単に抽象クラスを使用します。Microsoft VC ++は、これを内部的にどこかに定義するための1つのステップを取りました。

typedef struct interface;

...したがって、引き続きinterfaceキーワードを使用できます(「実際の」キーワードとして強調表示されることもあります)が、実際のコンパイラに関する限り、これは単なる構造体です。

これをどこで使用しますか?実行中のオブジェクトテーブルの例に戻りましょう。基本クラスが持っているとしましょう...

仮想void print()= 0;

抽象クラスがあります。ランタイムオブジェクトテーブルを使用するクラスはすべて、同じ基本クラスから派生します。基本クラスには、登録/登録解除のための共通コードが含まれています。しかし、それ自体ではインスタンス化されません。これで、派生クラス(要求、リスナー、クライアント接続オブジェクトなど)を使用できます。各クラスはprint()を実装するため、プロセスにアタッチして実行中のものを尋ねると、各オブジェクトは自身の状態を報告します。

抽象クラス/インターフェースの例は数え切れないほどあり、シングルトンを使用するよりもはるかに頻繁に使用する(または使用する必要がある)ことは間違いありません。要するに、基本型で動作し、実際の実装に縛られないコードを書くことができます。これにより、あまり多くのコードを変更することなく、後で実装を変更できます。

別の例を示します。ロガーCLogを実装するクラスがあるとしましょう。このクラスは、ローカルディスク上のファイルに書き込みます。私はレガシーの100,000行のコードでこのクラスの使用を開始します。あらゆる所に。誰かが言うまで、人生は良いものです。ちょっとファイルの代わりにデータベースに書き込みましょう。ここで新しいクラスを作成し、CDbLogと呼び、データベースに書き込みます。100,000行を通過し、CLogからCDbLogにすべてを変更する手間を想像できますか?または、次のようにすることもできます。

interface ILogger {
    virtual void write( const char* format, ... ) = 0;
};

class CLog : public ILogger { ... };

class CDbLog : public ILogger { ... };

class CLogFactory {
    ILogger* GetLog();
};

すべてのコードがILoggerインターフェイスを使用していた場合、変更する必要があるのはCLogFactory :: GetLog()の内部実装だけです。残りのコードは、指を離さなくても自動的に機能します。

インターフェイスと優れたオブジェクト指向設計の詳細については、C#での叔父ボブのアジャイルの原則、パターン、および実践を強くお勧めします。この本は、抽象化を使用した例で満たされ、すべてのことをわかりやすく説明しています。


4

推奨または必要なシングルトンを使用している場合 それは良い習慣ですか?

決して。さらに悪いことに、彼らは取り除くための絶対的な雌犬ですので、一度この間違いを犯すと、何年もあなたを悩ませることができます。

抽象クラスとインターフェースの違いは、C ++では絶対にありません。通常、派生クラスの一部の動作を指定するインターフェイスがありますが、すべてを指定する必要はありません。これにより、より制限された仕様を満たすクラスを交換できるため、コードがより柔軟になります。ランタイム抽象化が必要な場合、ランタイムインターフェイスが使用されます。


インターフェイスは抽象クラスのサブセットです。インターフェイスは、メソッドが定義されていない抽象クラスです。(コードをまったく含まない抽象クラス。)
ロボット

1
@StevenBurnap:多分他の言語で。
DeadMG

4
「インターフェース」は、C ++の単なる規則です。使用されているのを見たとき、それは純粋な仮想メソッドのみを持ち、プロパティを持たない抽象クラスです。もちろん、古いクラスを書いて、名前の前に「I」を叩くことができます。
ロボット

これは、人々がこの投稿に答えることを期待した方法です。一度に一つの質問。とにかく、知識を共有してくれてありがとう。このコミュニティはに投資する時間の価値がある。
appoll

3

シングルトンは、特定のオブジェクトの複数のコピーが必要ない場合に役立ちます。そのクラスのインスタンスは1つだけである必要があります。

2つ以上のインスタンスの固定数を持つシングルトンはマルチトンであり、データベース接続プーリングなどを考えます。

インターフェイスは、オブジェクト間の相互作用をモデル化するのに役立つ、明確に定義されたAPIを指定します。場合によっては、いくつかの共通の機能を備えたクラスのグループを作成できます。その場合、実装で複製する代わりに、インターフェイスにメソッド定義を追加して抽象クラスに変換できます。

すべてのメソッドが実装されている抽象クラスを持つこともできますが、サブクラス化せずにそのまま使用してはならないことを示すために抽象としてマークします。

注:インターフェースと抽象クラスは、多重継承などのC ++の世界ではそれほど違いはありませんが、Javaなどでは意味が異なります。


非常によく言いました!+1
jmort253

3

あなたがそれについて考えるのをやめたら、そのすべては多型についてです。渡すコードに応じて、考えられる以上のことを実行できるコードを1回作成できるようにする必要があります。

次のPythonコードのような関数があるとします。

function foo(objs):
    for obj in objs:
        obj.printToScreen()

class HappyWidget:
    def printToScreen(self):
        print "I am a happy widget"

class SadWidget:
    def printToScreen(self):
        print "I am a sad widget"

この関数の良い点は、オブジェクトが「printToScreen」メソッドを実装している限り、オブジェクトのリストを処理できることです。幸せなウィジェットのリスト、悲しいウィジェットのリスト、またはそれらを組み合わせたリストを渡すこともできますが、foo関数はそれでも正しく機能します。

一連のメソッド(この場合はprintToScreen)をインターフェイスとして実装する必要があるというこのタイプの制限を参照し、すべてのメソッドを実装するオブジェクトはインターフェイスを実装すると言われています。

Pythonのような動的でアヒル型の言語について話していたら、基本的にはもう終わりです。ただし、C ++の静的型システムでは、関数内のオブジェクトにクラスを指定する必要があり、その初期クラスのサブクラスでのみ機能します。

void foo( Printable *objs[], int n){ //Please correctme if I messed up on the type signature
    for(int i=0; i<n; i++){
        objs[i]->printToScreen();
    }
}

この場合、Printableクラスが存在する唯一の理由は、printToScreenメソッドが存在する場所を提供することです。printToScreenメソッドを実装するクラス間で共有実装は存在しないため、Printableを、共通階層内の類似クラスをグループ化する方法としてのみ使用される抽象クラスにすることは理にかなっています。

C ++では、absctractクラスとインターフェイスの概念は少しぼやけています。それらをより適切に定義したい場合、抽象クラスは考えていることですが、インターフェイスは通常、オブジェクトが公開する可視メソッドのセットのより一般的で言語を超えたアイデアを意味します。(Javaなどの一部の言語では、インターフェース用語を使用して、抽象基底クラスのようなものをより直接的に参照しています)

基本的に、具象クラスはオブジェクトの実装方法を指定し、抽象クラスは他のコードとのインターフェース方法を指定します。関数をより多態的にするには、意味がある場合はいつでも抽象スーパークラスへのポインターを受け取るようにしてください。


シングルトンに関しては、静的メソッドまたは単純な古い関数のグループで置き換えられることが多いため、実際にはまったく役に立ちません。ただし、オブジェクトを使用したくない場合でも、オブジェクトの使用を強制する何らかの制限があるため、シングルトンパターンが適切です。


ところで、「インターフェイス」という言葉はJava言語で特定の意味を持っているとコメントしている人もいるかもしれません。しかし、今のところはもっと一般的な定義を守る方が良いと思います。


1

インターフェース

これまでになかった問題を解決するツールの目的を理解することは困難です。プログラミングを始めてからしばらくの間、インターフェイスを理解していませんでした。私たちは彼らが何をしたかを理解しますが、なぜあなたがそれを使いたいのか分かりませんでした。

ここに問題があります-あなたは何をしたいのか知っていますが、あなたはそれをするための複数の方法を持っています、またはあなたは後でそれをする方法を変えるかもしれません。あなたが無知なマネージャーの役​​割を演じることができればいいでしょう-いくつかの注文をkえ、それがどのように行われても気にせずにあなたが望む結果を得ることができます。

小さなウェブサイトがあり、ユーザーのすべての情報をcsvファイルに保存するとします。最も洗練されたソリューションではありませんが、お母さんのユーザーの詳細を保存するには十分に機能します。その後、サイトが離陸し、10,000人のユーザーがいます。たぶん、適切なデータベースを使用するときです。

最初は賢い人なら、これが来るのを見て、csvに直接保存するための呼び出しを行わなかったでしょう。代わりに、どのように実装されたとしても、それを行うために必要なことを考えます。さんとしましょうstore()retrieve()。あなたは作るPersisterための抽象メソッドとのインタフェースをstore()し、retrieve()そして作成しCsvPersister、実際にそれらのメソッドを実装するサブクラスを。

後で、DbPersistercsvクラスが実際に行った方法とはまったく異なる方法で、実際のストレージとデータの取得を実装するを作成できます。

素晴らしいことは、あなたが今しなければならないのは変化です

Persister* prst = new CsvPersister();

Persister* prst = new DbPersister();

これで完了です。あなたの呼び出しprst.store()prst.retrieve()、すべてまだ作業は、彼らは単に「舞台裏」異なった方法で処理しています。

今、あなたはまだcvsとdbの実装を作成しなければならなかったので、あなたはまだボスであるという贅沢を経験していません。本当の利点は、他の誰かが作成したインターフェイスを使用している場合に明らかです。他の誰かが作成するために親切にした場合CsvPersister()DbPersister()、すでに、あなたは1を選択し、必要なメソッドを呼び出す必要がありますだけ。後で別のプロジェクトを使用する場合、または別のプロジェクトで使用する場合は、その機能がすでにわかっています。

私はC ++で本当にさびているので、いくつかの一般的なプログラミング例を使用します。コンテナは、インターフェースがどのようにあなたの生活を楽にするかを示す素晴らしい例です。

あなたは持つことができArrayLinkedListBinaryTree、などのすべてのサブクラスのContainerようなメソッドを持っていますinsert()find()delete()

リンクされたリストの途中に何かを追加するとき、リンクされたリストが何であるかを知る必要さえありません。呼び出すだけでmyLinkedList->insert(4)、リストを魔法のように繰り返してそこに貼り付けます。リンクされたリストがどのように機能するかを知っていても(実際にそうすべきです)、その特定の機能を調べる必要はありませんContainer

抽象クラス

抽象クラスはインターフェイスにかなり似ています(技術的にはインターフェイス抽象クラスですが、ここではメソッドの一部が具体化された基本クラスを意味します。

あなたがゲームを作成しているとしましょう。そして、敵がプレイヤーのすぐそばにいるときを検出する必要があります。Enemyメソッドを持つ基本クラスを作成できますinRange()。敵にはさまざまなものがありますが、敵の範囲を確認する方法は一貫しています。したがって、Enemyクラスには範囲をチェックするための肉付けされたメソッドがありますが、敵のタイプ間で類似性を共有しない他のことのための純粋な仮想メソッドがあります。

これの良いところは、範囲検出コードを台無しにしたり、微調整したい場合、一箇所で変更するだけです。

もちろん、インターフェイスと抽象基本クラスには他にも多くの理由がありますが、それらを使用する理由はいくつかあります。

シングルトン

私は時々それらを使用します、そして、私は彼らによって決して燃やされませんでした。それは、他の人々の経験に基づいて、彼らがいつか私の人生を台無しにしないと言うことではありません。

ここに、より経験豊富で警戒心の強い人々からのグローバルな状態に関する良い議論があり ます。


1

動物界には哺乳類である様々な動物がいます。ここで、哺乳類は基本クラスであり、さまざまな動物がそこから派生しています。

哺乳類が通り過ぎるのを見たことがありますか?はい、私は確信しています-しかし、それらはすべての種類の哺乳類でしたか?

文字通り単なる哺乳類であるものを見たことがない。それらはすべて哺乳類のタイプでした。

クラス哺乳類は、さまざまな特性とグループを定義する必要がありますが、物理的なエンティティとしては存在しません。

したがって、これは抽象基本クラスです。

哺乳類はどのように移動しますか?彼らは歩いたり、泳いだり、飛んだりしますか?

哺乳類レベルで知る方法はありませんが、すべての哺乳類は何らかの形で移動する必要があります(これは例を簡単にするための生物学的法則であると言いましょう)。

したがって、MoveAround()は、このクラスから派生するすべての哺乳類が異なる方法で実装できる必要があるため、仮想関数です。

ただし、すべての哺乳類は移動する必要があり、哺乳類レベルで実行することは不可能であるため、すべての哺乳類がMoveAroundを定義する必要があります。すべての子クラスで実装する必要がありますが、基本クラスでは意味がありません。

したがって、MoveAroundは純粋な仮想関数です。

アクティビティは許可するが、トップレベルでの実行方法を定義できないクラス全体がある場合、すべての機能は純粋な仮想であり、これはインターフェイスです。
たとえば、ロボットをコーディングして私に提出して戦場で戦うゲームがある場合、呼び出す関数名とプロトタイプを知る必要があります。「インターフェイス」が明確である限り、どのように実装するかは気にしません。したがって、キラーロボットを作成するために派生するインターフェイスクラスを提供できます。

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