2012-02-21 4 views
7

既存のプロジェクトでは、私自身の扱いを定義するためにSingletonとして宣言されたControllerクラス(MVC)を継承します。このシングルトンクラスを適切に派生させるにはどうすればよいですか?C++シングルトンクラス - 継承良い練習

まず、私は文脈を拡大し、この継承を必要とします。

私が既存のソフトウェアに追加したアプリケーションは、実行したいのとほぼ同じタスクを実行するMVCモジュールを使用したいと考えています。それは署名とわずかな修正まで同じ方法を使用しています。私自身のMVCモジュールを書き直すことは、確かにコードの重複になります。既存のモジュールは、ソフトウェアの別の部分への適用に向けて本質的に指向しており、私は単に同じモジュールを使用することはできません。しかし、コントローラーがシングルトンのモデルビューコントローラーパターンとして記述されています。私はすでにViewを派生しました。

第二に、私はクラシッククラスを派生させることができるのではないかと疑う。

継承されたクラスのコンストラクタを呼び出すと、親クラスのgetinstance()が呼び出され、派生クラス(?)からオブジェクトが返されません。

第3に、対処方法がわかります。コメントしてください/改善に役立つ!

私はAbstractControllerを呼び出すことができるクラスでシングルトンクラス全体をコピーします。私はこのクラスを2度派生します。最初の子はシングルトンで、親クラスのすべての処理を採用しています。 2番目の子は、独自の再定義された処理を持つ、アプリケーションの私の部分のコントローラです。

ありがとうございます!

+0

ほとんどの場合、クラスにシングルトンの基本クラスがある場合、それは完全に無用です:派生クラスのインスタンスは決して作成できません。そのようなインスタンスはそれぞれ基本クラスのインスタンスになるためあなたはそれらの別のものを作ることを許されていません。 (私は "合理的な"例外を想像することができますが、基底クラスが*念頭に置かれているという考えで具体的に*設計されている場合のみ) – Hurkyl

答えて

3

私はあなたが完全に対処している状況を理解していませんし、シングルトンから派生することが可能かどうかはシングルトンがどのように実装されているかによって大きく異なります。

しかし、あなたは「良い習慣」を言及したので、質問を読むときに頭に来るいくつかの一般的なポイントがあります:

  1. 継承は通常、コードの再利用を実現するための最良のツールではありません。参照してください:Prefer composition over inheritance?

  2. シングルトンと "良い実践"を使用して一般的に一緒に行くことはありません!参照:What is so bad about singletons?

お役に立てば幸いです。

21

真実は、シングルトンと継承はうまくいっていません。

ええ、ええ、シングルトンの愛好家とGoFのカルトは、「あなたはあなたのコンストラクタは保護させる場合だけでなく、...」と「あなたはgetInstance方法を有することがを持っていないと言って、このすべてのための私の上になりますクラスでは、あなたはそれを置くことができます... "、しかし、彼らは私のポイントを証明しています。シングルトンは、シングルトンとベースクラスの両方になるために、いくつかのフープを飛び越えなければなりません。

ただ質問に答えるには、シングルトンの基本クラスがあるとします。それは、ある程度、相続によってその独裁を強制することもできます。 (コンストラクタはプライベートにならないときに動作するいくつかの機能のうちの1つを行います。Baseが既に存在する場合は例外がスローされます)Baseを継承するクラスDerivedもあるとします。継承が許可されているので、他のサブクラスであるBaseがあり、それはDerivedから継承される場合と継承されない場合があります。

しかし、問題があります。あなたが既に遭遇しているか、まもなく間に合うでしょうか。すでにオブジェクトを構築せずにBase::getInstanceと呼ぶと、NULLポインタが返されます。シングルトンオブジェクトが存在するかどうかを取得したい(BaseDerivedOtherのいずれか)。しかし、これを行うのは難しいですし、すべてのルールに従ってください。そのためにはいくつかの方法しかありません。そのすべてにいくつかの欠点があります。

  • Baseを作成して戻すことができます。ネジDerivedおよびOther。最終結果:Base::getInstance()は常に正確にBaseを返します。子クラスは決して遊ぶことができません。 Kindaは目的を破る、IMO。

  • 具体的にはDerivedが必要な場合は、getInstanceを弊社の派生クラスに入れて、発信者にDerived::getInstance()と言うことができます。これは、呼び出し元が具体的にDerivedを要求することを知る必要があるため、その実装に結びつくため、カップリングが大幅に増加します。

  • 最後のものの変形を行うことができますが、インスタンスを取得する代わりに関数が作成します。 (それが終わってから、関数の名前をinitInstanceに変更してみましょう。なぜなら、何を得るかは特に気にしないからです。新しいDerivedが作成され、それを1つのTrueインスタンスとして設定しています)。だから、(まだ行方不明の任意奇数判定を禁止する)

が、それはちょっと、このようにうまくいく...

class Base { 
    static Base * theOneTrueInstance; 

    public: 
    static Base & getInstance() { 
     if (!theOneTrueInstance) initInstance(); 
     return *theOneTrueInstance; 
    } 
    static void initInstance() { new Base; } 

    protected: 
    Base() { 
     if (theOneTrueInstance) throw std::logic_error("Instance already exists"); 
     theOneTrueInstance = this; 
    } 

    virtual ~Base() { } // so random strangers can't delete me 
}; 

Base* Base::theOneTrueInstance = 0; 


class Derived : public Base { 
    public: 
    static void initInstance() { 
     new Derived; // Derived() calls Base(), which sets this as "the instance" 
    } 

    protected: 
    Derived() { } // so we can't be instantiated by outsiders 
    ~Derived() { } // so random strangers can't delete me 
}; 

そして、あなたのinitコードで、あなたはBase::initInstance();Derived::initInstance();を言う、これに応じて、あなたを入力シングルトンを望みます。もちろんDerived特有の関数を使用するには戻り値をBase::getInstance()からキャストしなければなりませんが、キャストなしでで定義された関数を使用できます。Derivedで上書きされます。それを行うためのこの方法もかかわらず、それ自身の欠点の数を持っていることを

注:

  • それは、基本クラスで独身の施行の負担のほとんどを置きます。ベースにこのような機能がなく、それを変更できない場合は、ちょっとしたことになります。各クラスが保護デストラクタを宣言する必要がある、または誰かが一緒に来ることができ、適切に(で)それを鋳造後のインスタンスを削除し、そして -

  • 基底クラスは、しかし、責任のすべてを取ることができませんすべてが地獄に行きます。さらに悪いことに、これはコンパイラによって強制できません。

  • 私たちは、インスタンスが削除されるのを防ぐために、保護されたデストラクタを使用しているため、コンパイラが賢明でない限り、ランタイムでもプログラムが終了するとインスタンスは正しく削除されません。さようなら、RAII ...こんにちは "メモリリークが検出されました"という警告。 (もちろん、メモリはまともなOSによって最終的に再利用されます。しかし、デストラクタが実行されない場合、それに依存してクリーンアップを行うことはできません。終了する前に何らかのクリーンアップ関数を呼び出す必要があります。これは、RAIIが与えることができる保証の近くにあなたを与えるものではありません)。initInstance IMOは、誰もが見ることができるAPIに本当に属していません。必要ならば、initInstanceをプライベートにして、init関数をfriendにすることができますが、あなたのクラスはそれ自身の外側のコードについて仮定しています。

また、上記のコードはスレッドセーフではありません。あなたがそれを必要とするならば、あなたはあなた自身である。

真剣にも、苦痛の少ないルートは、一重を強制しようとすることを忘れることです。インスタンスが1つしかないことを確認する最も複雑な方法は、を作成してを作成することです。複数の場所で使用する必要がある場合は、依存関係注入を検討してください。 (それを必要とするものにオブジェクトを渡すことになるのは、非フレームワーク版です:P)シングルトンや継承について間違っていることを証明するために、上記のものを設計し、自分自身に再確認しましたその組み合わせはです。私は実際には実際のコードでそれをやってみることをお勧めしません。

+1

これは素晴らしい解答であり、ユーモア。それはそれがSOレーダーの下で飛んできたことは驚くべきことです。 –