web-dev-qa-db-ja.com

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

私は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 . シングルトン

[〜#〜] how [〜#〜]1つのオブジェクトのみを持つというクラスの制限は機能しますか?

[〜#〜]できます[〜#〜][〜#〜]のみ[〜#〜]2インスタンス?または多分3?

[〜#〜] [〜#〜]が推奨/必要なシングルトンを使用している場合?いい練習ですか?

2 . 抽象クラ​​ス

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

virtual void print ()=0;

そうするでしょう?

[〜#〜]なぜ[〜#〜]オブジェクトが不要なクラスが必要ですか?

3.インターフェース

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

[〜#〜]何[〜#〜]は、それらのうちの2つの主な違いですか?

前もって感謝します!

13
appoll

1。シングルトン

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

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

シングルトンの詳細を知りたい場合は、 Wikipedia から始め、特にC++の場合は この投稿 から始めます。

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

2。抽象クラス

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

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

Mammalクラスを定義し、それをDogおよびCatに継承するとします。考えてみると、最初に哺乳類の種類を知る必要があるので、哺乳類の純粋なインスタンスを持つことはあまり意味がありません。

継承されたクラスでのみ意味をなすMakeSound()と呼ばれるメソッドが存在する可能性がありますが、すべての哺乳動物が作成できる共通のサウンドはありません(ここでは、哺乳動物のサウンドのケースを作成しようとしない例にすぎません)。

つまり、すべての哺乳動物に実装されている共通の動作があるため、Mammalは抽象クラスである必要がありますが、実際にはインスタンス化されているわけではありません。これが抽象クラスの背後にある基本的な概念ですが、学ぶべきことが他にもあります。

3。インターフェース

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

基本的にあなたが探している振る舞いは、他のオブジェクトが基礎となる実装を気にすることなく対話できるコントラクトを定義することです。クラスを純粋に抽象化すると、すべての実装が別の場所に属することになります。そのため、そのクラスの目的は、そのクラスが定義するコントラクトについてのみです。これは、OO)の非常に強力な概念であり、さらに詳しく検討する必要があります。

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

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

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

17
Alex

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

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

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

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

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

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

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

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

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

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

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

typedef struct interface;

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

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

virtual 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()の内部実装だけです。残りのコードは、指を離さなくても自動的に機能します。

インターフェイスと良いOOデザインの詳細については、ボブおじさんの C#でのアジャイル原則、パターン、およびプラクティス を強くお勧めします。この本には、抽象化し、すべてのプレーンな言語の説明を提供します。

8
DXM

推奨/必要なシングルトンを使用しているのはいつですか?いい練習ですか?

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

C++では、抽象クラスとインターフェースの違いはまったくありません。一般に、派生クラスのいくつかの動作を指定するためのインターフェイスがありますが、すべてを指定する必要はありません。これにより、より限定的な仕様を満たすクラスを交換できるため、コードがより柔軟になります。ランタイムインターフェースは、ランタイムアブストラクションが必要な場合に使用されます。

4
DeadMG

Singletonは、特定のオブジェクトの複数のコピーが不要な場合に役立ちます。そのクラスのインスタンスは1つだけでなければなりません。これは、グローバル状態を維持し、再入不可を処理する必要があるオブジェクトに使用されます何らかの方法でコードなど.

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

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

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

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

3
Alok

あなたがそれについて考えるのをやめるなら、それは多態性についてのすべてです。あなたはそれを渡すものに応じて1つ以上のことを考えることができるコードを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)を実装する必要があるというこのタイプの制限をinterfaceと呼び、すべてのメソッドを実装するオブジェクトはインターフェースを実装すると言われています。

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をabstract classにすることは意味があります。これは、共通の階層で類似のクラスをグループ化する方法としてのみ使用されます。

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

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


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


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

3
hugomg

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

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

あなたは文字通りほんのほんのほんの一部である何かを見たことがありません。彼らはすべての種類の哺乳類でした。

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

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

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

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

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

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

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

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

1
Stefan

インターフェース

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

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

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

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

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

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

_Persister* prst = new CsvPersister();
_

_Persister* prst = new DbPersister();
_

そしてあなたは終わった。 prst.store()prst.retrieve()への呼び出しはすべて機能しますが、「舞台裏」で処理が異なるだけです。

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

私は自分のC++に本当に錆びているので、いくつかの一般的なプログラミング例を使用します。コンテナは、インターフェースがあなたの生活を楽にする方法の良い例です。

ArrayLinkedListBinaryTreeなどを持つことができます。Containerのすべてのサブクラスには、insert()find()delete()

リンクされたリストの途中に何かを追加するとき、リンクされたリストが何であるかを知る必要さえありません。 myLinkedList->insert(4)を呼び出すだけで、魔法のようにリストを反復処理し、そこに貼り付けます。リンクリストがどのように機能するかを知っていても(実際にそうする必要があります)、特定の関数を調べる必要はありません。おそらく、以前に別のContainerを使用することによって何が得られたかがわかっているためです。

抽象クラス

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

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

これの良い点は、範囲検出コードをめちゃくちゃにしたり、微調整したりする場合、1か所で変更するだけで済みます。

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

シングルトン

私は時々それらを使用し、私はそれらに火傷したことはありません。他の人の経験に基づいて、彼らがいつの日か私の人生を台無しにしないというわけではありません。

より経験豊富で注意深い人々からのグローバルな状態についての良い議論はここにあります: なぜグローバルな状態はとても邪悪なのですか?

1
Dean