デザインパターン面接の重要ポイント

シングルトンパターン

シングルトンデザインパターンとは何か、その実装方法について説明してください

1. シングルトンパターンの定義

あるクラスが唯一のインスタンスのみを持つことを保証し、そのインスタンスへのグローバルアクセスポイントを提供する設計パターン。このインスタンスはすべてのプログラムモジュールから共有される。 前提条件
  • そのクラスは複製できない。
  • そのクラスのインスタンス生成は公開されていない。
C++の場合、コンストラクタ、コピーコンストラクタ、代入演算子は公開呼び出しを禁止する必要がある。

2. シングルトンパターンの実装方法

シングルトンパターンには主に2つの形式があり、それぞれ「遅延初期化型(ラージハン)」と「即時初期化型(イーハン)」である。それぞれの実装方法は以下の通り: 遅延初期化型(2種類)
  • 静的ポインタ+必要な時に初期化
  • 局所静的変数
即時初期化型(2種類)
  • 静的オブジェクトの直接定義
  • 静的ポインタ+外部でのnewによる初期化

3. 詳細な解説

1. 遅延初期化型:
遅延初期化型の特徴は遅延ロードであり、設定ファイルなどに適用される。設定ファイルのインスタンスは実際に使用されるまで読み込まれず、必要がない限りクラスのインスタンス化は行われない。つまり、初めてクラスインスタンスが必要になるタイミングでインスタンス化される。
以下は遅延初期化型の実装例:
遅延初期化型実装1:静的ポインタ+必要な時に初期化
template<typename T>
class Singleton {
public:
    static T& getInstance() {
        if (!value_) {
            value_ = new T();
        }
        return *value_;
    }

private:
    Singleton();
    ~Singleton();
    static T* value_;
};

template<typename T>
T* Singleton<T>::value_ = nullptr;
単スレッド環境では上記コードは正しく動作する。しかしマルチスレッド環境ではスレッドセーフではない。マルチスレッド環境における問題点を以下のように説明する:
  • スレッドAとスレッドBがgetInstance関数にアクセスすると仮定する。スレッドAが関数に入り、if条件をチェックする。初回の実行であり、value_がNULLであるためif条件が成立し、オブジェクトのインスタンス作成の準備を行う。
  • しかし、スレッドAがオブジェクトのインスタンス作成前にOSのスケジューラによって割り込まれて停止され、制御がスレッドBに移る。
  • スレッドBもif条件を確認し、value_がまだNULLであることを発見する。なぜなら、スレッドAがインスタンス化を完了する前に割り込まれたためである。スレッドBがオブジェクトを作成し、正常に返却する。
  • その後、スレッドAが再開して再度newを実行し、別のオブジェクトを生成する。この結果、2つのスレッドがそれぞれ異なるオブジェクトを生成し、シングルトンの唯一性を破壊する。
スレッドセーフ以外にも、メモリリークの問題がある。newで作成されたオブジェクトは解放されない。
改善版
template<typename T>
class Singleton {
public:
    static T& getInstance() {
        if (!value_) {
            static CGarbo garbo; // ステートックローカル変数を使用し、スレッドセーフを保証
            value_ = new T();
        }
        return *value_;
    }

private:
    class CGarbo {
    public:
        ~CGarbo() {
            if (Singleton::value_) {
                delete Singleton::value_;
            }
        }
    };

    Singleton();
    ~Singleton();
    static T* value_;
};

template<typename T>
T* Singleton<T>::value_ = nullptr;
プログラム終了時、システムはSingletonクラスの静的メンバーGarboのデストラクタを呼び出す。このデストラクタはシングルトンの唯一インスタンスを削除する。この方法によるシングルトンオブジェクトの解放には以下の特徴がある:
  • シングルトンクラス内部に専用のネストクラスを定義
  • シングルトンクラス内に解放用の静的メンバをプライベートに定義
  • プログラム終了時にグローバル変数のデストラクタが呼ばれるという性質を利用して、解放のタイミングを決定
遅延初期化型実装2:局所静的変数
(省略)
複数のシングルトンオブジェクトが相互依存している場合、プログラムのクラッシュの危険性がある。理由は、コンパイラが静的メンバ変数の初期化順序と破棄順序を定義していないためである。
2. 即時初期化型
シングルトンクラスの定義時にインスタンス化を行う。main関数が実行される前、グローバルスコープの静的メンバ変数m_Instanceは既に初期化されているため、マルチスレッドの問題はない。
即時初期化型実装1:静的オブジェクトの直接定義
// .hファイル
class Singleton {
public:
    static Singleton& GetInstance();

private:
    Singleton() {}
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);

private:
    static Singleton m_Instance;
};

// CPPファイル
Singleton Singleton::m_Instance; // クラス外で定義 - 忘れないように

Singleton& Singleton::GetInstance() {
    return m_Instance;
}

// 関数呼び出し
Singleton& instance = Singleton::GetInstance();
欠点 プログラム開始時にクラスのインスタンスが作成される。もしSingletonオブジェクトの作成コストが高く、使用頻度が低い場合、リソース効率の観点からは遅延初期化型よりも劣る。しかし応答時間の観点からは、遅延初期化型よりも優れている。

ファクトリーパターンについて説明し、実装方法および利点を述べてください

1. ファクトリーパターンの定義

オブジェクトの生成を抽象化し、オブジェクトの作成処理を工場に委ねる設計パターン。これにより、クラス間の結合度が低くなり、柔軟性が向上する。

2. ファクトリーパターンの分類

ファクトリーパターンは生成に関するパターンであり、大きく3つのタイプに分けることができる:シンプルファクトリーパターン、ファクトリーメソッドパターン、アブストラクトファクトリーパターン

1. シンプルファクトリーパターン

主な特徴は、工場クラス内で条件分岐を行い、対応する製品を生成すること。新しい製品を追加する際には工場クラスの修正が必要になる。 例:プロセッサコアを製造する工場があり、1つの工場が2種類のプロセッサコアを製造できる。顧客はどのコアを必要とするかを明示的に工場に伝える必要がある。
コード例(シンプルファクトリーパターン)
// サンプルコード(シンプルファクトリーパターン)
enum CTYPE {COREA, COREB};     
class SingleCore    
{    
public:    
    virtual void Show() = 0;  
};    
// コアA    
class SingleCoreA: public SingleCore    
{    
public:    
    void Show() { cout<<"SingleCore A"<<endl; }    
};    
// コアB    
class SingleCoreB: public SingleCore    
{    
public:    
    void Show() { cout<<"SingleCore B"<<endl; }    
};    
// 一つの工場、2種類のコアを製造、内部で条件分岐    
class Factory    
{    
public:     
    SingleCore* CreateSingleCore(enum CTYPE ctype)    
    {    
        if(ctype == COREA) // 工場内部で条件分岐    
            return new SingleCoreA(); // コアAを製造    
        else if(ctype == COREB)    
            return new SingleCoreB(); // コアBを製造    
        else    
            return NULL;    
    }    
};
利点:シンプルファクトリーパターンは、要求に応じて動的に必要なクラスのオブジェクトを生成でき、利用者はオブジェクトの作成方法を知らなくてもよく、各モジュールが独立して機能し、システムの結合度を下げられる。 欠点:新しいコアタイプを追加する際に、工場クラスを修正する必要があり、開閉原則(開発は拡張可能だが修正は不可能)に反する。

2. ファクトリーメソッドパターン

定義:オブジェクト生成インターフェースを定義し、どのクラスをインスタンス化するかをサブクラスに任せる。このパターンにより、インスタンス化の処理をサブクラスに遅延させることができる。 プロセッサコアの工場が利益を得て、B型コア専用の工場を新たに設立し、元の工場はA型コア専用に変更した。顧客は工場の種類を選択すればよい。A型コアが必要ならA工場に、B型コアが必要ならB工場に依頼する。工場にどの型のコアを必要とするかを指示する必要はない。
ファクトリーメソッドパターン
class SingleCore    
{    
public:    
    virtual void Show() = 0;  
};    
// コアA    
class SingleCoreA: public SingleCore    
{    
public:    
    void Show() { cout<<"SingleCore A"<<endl; }    
};    
// コアB    
class SingleCoreB: public SingleCore    
{    
public:    
    void Show() { cout<<"SingleCore B"<<endl; }    
};    
class Factory    
{    
public:    
    virtual SingleCore* CreateSingleCore() = 0;  
};    
// Aコアを製造する工場    
class FactoryA: public Factory    
{    
public:    
    SingleCoreA* CreateSingleCore() { return new SingleCoreA; }    
};    
// Bコアを製造する工場    
class FactoryB: public Factory    
{    
public:    
    SingleCoreB* CreateSingleCore() { return new SingleCoreB; }    
};
利点:拡張性が高く、開閉原則に従い、新しい製品を追加する際には、対応する製品クラスと工場サブクラスを追加するだけで良い。 欠点3. アブストラクトファクトリーパターン
アブストラクトファクトリーパターン
// コア    
class SingleCore     
{    
public:    
    virtual void Show() = 0;  
};    
class SingleCoreA: public SingleCore      
{    
public:    
    void Show() { cout<<"Single Core A"<<endl; }    
};    
class SingleCoreB :public SingleCore    
{    
public:    
    void Show() { cout<<"Single Core B"<<endl; }    
};    
// マルチコア    
class MultiCore      
{    
public:    
    virtual void Show() = 0;  
};    
class MultiCoreA : public MultiCore      
{    
public:    
    void Show() { cout<<"Multi Core A"<<endl; }    
    
};    
class MultiCoreB : public MultiCore      
{    
public:    
    void Show() { cout<<"Multi Core B"<<endl; }    
};    
// 工場    
class CoreFactory      
{    
public:    
    virtual SingleCore* CreateSingleCore() = 0;  
    virtual MultiCore* CreateMultiCore() = 0;  
};    
// 工場A、A型プロセッサ専用    
class FactoryA :public CoreFactory    
{    
public:    
    SingleCore* CreateSingleCore() { return new SingleCoreA(); }    
    MultiCore* CreateMultiCore() { return new MultiCoreA(); }    
};    
// 工場B、B型プロセッサ専用    
class FactoryB : public CoreFactory    
{    
public:    
    SingleCore* CreateSingleCore() { return new SingleCoreB(); }    
    MultiCore* CreateMultiCore() { return new MultiCoreB(); }    
};
利点:抽象工場クラスは複数の製品タイプを生成し、要求があれば関連する製品と工場サブクラスを追加することで対応できる。 欠点:新しい製品種別を追加するのが困難。抽象工場パターンでは、工場の抽象クラスで事前に製品の種類を決定しておく必要がある。もし将来追加される製品が事前に定義されていない場合、抽象工場クラスを変更する必要があり、これによりすべての工場サブクラスを修正する必要がある。

デコレーターパターンについて説明し、その利点と欠点を述べてください

デコレーターパターンの定義

既存のオブジェクト構造を変更せずに、動的にそのオブジェクトに新しい責任(機能追加)を付与する設計パターン。 利点
  • 継承の補完としての役割を果たし、継承より柔軟性があり、既存オブジェクトを変更することなく、動的に機能を追加できる。
  • 異なるデコレータークラスやそれらの組み合わせを使用することで、さまざまな効果を実現できる。
  • 完全に開閉原則を満たす。
欠点
  • 多くのサブクラスが生成され、過剰に使用するとプログラムの複雑性が増す。

デコレーターパターンの構造と実装

通常、クラスの機能を拡張するには継承を使用するが、これは静的な特徴であり、結合度が高い。さらに機能を追加するほど、サブクラスは肥大化する。組み合わせを使用してラッパー(デコレーター)オブジェクトを作成し、本物のオブジェクトを包み込み、そのクラス構造を変更せずに追加機能を提供するのが、デコレーターパターンの目的である。以下にその基本構造と実装方法を示す。 デコレーターパターンには以下の要素が含まれる:
  • 抽象コンポーネント:追加責任を受けるオブジェクトの抽象インターフェースを定義
  • 具象コンポーネント:抽象コンポーネントを実装し、デコレーターオブジェクトによって機能を追加
  • 抽象デコレーター:抽象コンポーネントを継承し、具象コンポーネントのインスタンスを保持し、サブクラスで機能を拡張
  • 具象デコレーター:抽象デコレーターのメソッドを実装し、具象コンポーネントに追加の責任を付与

UML図

  • シンプルファクトリーパターンUML
  • ファクトリーメソッドのUML図
  • 抽象ファクトリーパターンのUML図
  • デコレーターパターン

タグ: シングルトンパターン ファクトリーパターン デコレーターパターン C++

7月2日 19:46 投稿