2

GUI コンポーネントの階層のルートである Component という抽象基本クラスがあるとします。このような場合、Button と Label の 2 つのサブクラスがあり、どちらも抽象クラスであり、具象クラスのそれぞれの階層のルートとして存在します。

Button から継承する具象クラスには、RoundButton および SquareButton が含まれる場合があります。

Label から継承する具体的なクラスには、TextLabel と PictureLabel が含まれる場合があります。

最後に、Component オブジェクトのコレクションを保持する集約 Container クラスがあるとします。

問題は、コンポーネント オブジェクトへのポインターがあることですが、それらがボタンまたはラベルのいずれかであることを識別する必要があります。たとえば、すべてのボタンの内部テキストに大きなフォントを使用するように指定したい場合、コンテナ内のすべてのコンポーネント オブジェクトを反復処理して、どのボタンがボタンであるかを何らかの方法で特定し、ボタン固有のメソッドを呼び出すことができます。

これらのコンポーネント「ファミリ」が自身を識別する 1 つの方法は、文字列を使用することです。

class Component {
public:
    virtual char const * const getFamilyID() const = 0;
};

// In Button.h
char const * const COMPONENT_BUTTON = "button";

class Button : public Component {
public:
    virtual char const * const getFamilyID() const { return COMPONENT_BUTTON; };
};

// Code sample
if (strcmp(component->getFamilyID(),COMPONENT_BUTTON) == 0)
    // It's a button!

これは、コンポーネントがこれらのファミリを定義するタスクをその子に任せているという点で疎結合です。どのファミリが存在するかを知る必要はありません。クライアントはさまざまなコンポーネント ファミリを認識している必要がありますが、何らかの操作で特定のコンポーネント ファミリをターゲットにしようとしている場合、それは避けられません。

ただし、非常に高いパフォーマンス要件があり、文字列の比較を避けたいとします。また、インライン化できるように、この関数を仮想化することは避けたほうがよいでしょう。また、Component のすべてのサブクラスがグローバル定数を宣言する必要がある場合は、何らかの方法で Component クラスを変更して、これを要件にするか不要にするのがよいでしょう。

この問題の解決策の 1 つは、Component.h で列挙子を定義することです。

enum COMPONENT_FAMILY {
    COMPONENT_BUTTON = 0,
    COMPONENT_LABEL,
    // etc...
};

この場合、 getFamilyID() は COMPONENT_FAMILY 列挙型を返すだけでよく、基本的には int を比較するだけです。残念ながら、これは、新しいコンポーネント ファミリをこの列挙型に「登録」する必要があることを意味します。これは簡単ですが、他のプログラマにとって完全に直感的ではありません。また、カーディナリティが非常に低い (理想的ではない) ことがわかっている非静的 COMPONENT_FAMILY メンバーを作成しない限り、メソッドは依然として仮想である必要があります。

この問題を解決する良い方法は何でしょうか? 私の場合、パフォーマンスが重要であり、enum ソリューションに似たもので十分に簡単に思えますが、より良い方法を見落としているのではないかと考えています。

--- 編集 ---
実際のシステムでは、同等のコンテナは各ファミリから 1 つのコンポーネントしか保存できないことをおそらく指摘する必要があることを認識しています。したがって、コンポーネントは実際には次のようなマップに格納されます。

std:map<COMPONENT_FAMILY, Component*>

ここでの私の簡単な例に当てはめると、これはコンテナに 1 つのボタン、1 つのラベルなどしか含めることができないことを意味します。

これにより、特定のタイプのコンポーネント (ログ時間) の存在を簡単に確認できます。したがって、この質問は、COMPONENT_FAMILY を表す方法と、コンポーネントをマップに追加するときにコンポーネントのタイプを決定する方法に関するものです。

つまり、コンポーネントの唯一の目的は、コンテナに追加された特定の機能として識別され、すべてのコンポーネントが一緒になってコンテナの特定の動作を定義することです。

したがって、コンポーネントのタイプを知る必要はありません。それは既に暗示されています。具体的には、コンテナに特定のタイプのコンポーネントを要求しています。私が必要としているのは、最初にマッピングできるように、コンポーネントがそのタイプを伝達する方法です。

4

6 に答える 6

5

それらをボタンまたはラベルとして識別する必要があります。

それはあなたの問題だ。この仮定は、よくあることですが、通常は間違っています。

  • なぜそれらを特定する必要があると思いますか?
  • その知識はコードのどの段階で重要ですか?

構築時にコントロールの UI 戦略を割り当てることで、具体的な型を知るための「要件」を回避できる可能性があります。基本クラスで、ペイント時 (またはいつでも) にその UI 戦略のフォント プロパティを取得します。

class IVisualStrategy
{
    ...
    virtual const Font& GetFont() const = 0;
    ...
};

class HeavyVisuals : public IVisualStrategy
{
    Font font_;
    ...
    HeavyVisuals() : font_(15, Bold) {}

    virtual const Font& GetFont() const { return font_; }
    ...
};

class LightVisuals : public IVisualStrategy
{
    Font font_;
    ...
    LightVisuals() : font_(12, Regular) {}

    virtual const Font& GetFont() const { return font_; }
    ...
};

ベースから取得:

class Control
{
    ...
private:
    void OnPaintOrSomething()
    {
        DrawTextWithFont(GetVisualStrategy().GetFont());
    }

    virtual const IVisualStrategy& GetVisualStrategy() const = 0;
};

class Button : public Control
{
    HeavyVisualStrategy visualStrategy_;
    ...
private:
    virtual const IVisualStrategy& GetVisualStrategy() const
    {
        return visualStrategy_;
    }
}

class Label : public Control
{
    LightVisualStrategy visualStrategy_;
    ...
private:
    virtual const IVisualStrategy& GetVisualStrategy() const
    {
        return visualStrategy_;
    }
}

より柔軟な設計は、IVisualStrategy への共有ポインターを具体的なコントロール クラスに保持し、コンストラクターで注入することです。それらを Heavy または Light にハード設定するのではありません。

また、このデザインのオブジェクト間でフォントを共有するには、Flyweight パターンを適用できます。

于 2011-06-07T13:29:33.227 に答える
4

動的キャストは、魔法の定数を導入せずにこれを行います。

if (Button * button = dynamic_cast<Button *>(component)) {
    // It's a button.
}

更新:タイプベースのマップキーの要件で質問を更新したので、動的キャストは機能しません。中央の列挙の結合を回避する 1 つの方法は、次のような静的キー ジェネレーターを使用することです。

class Component
{
public:
    virtual int getFamilyID() const = 0;
    static int generateFamilyID() 
    {
        static int generator = 0;
        return generator++;
    }
};

class Label
{
public:
    virtual int getFamilyID() const {return familyID;}
private:
    static const int familyID;
};

class Button
{
public:
    virtual int getFamilyID() const {return familyID;}
private:
    static const int familyID;
};

const int Label::familyID  = Component::generateFamilyID();
const int Button::familyID = Component::generateFamilyID();
于 2011-06-07T13:28:09.330 に答える
1

ダブルディスパッチャーパターンを使用する

http://en.wikipedia.org/wiki/Double_dispatch

于 2011-06-07T13:31:43.697 に答える
1

を使用するだけdynamic_castです。それが目的です。ああ、これの必要性は一般的に悪いことだと考えられています。

于 2011-06-07T13:33:23.320 に答える
0

指定されたクラスがインスタンスであるか、そのサブクラスの 1 つであるかdynamic_castをテストするために使用できます。ComponentButton

Button* btn = dynamic_cast<Button*>(component);
if (btn) {
    // it's a button!
    btn->setFontSize(150);
}
于 2011-06-07T13:28:51.967 に答える
0

ビジターパターンはあなたのケースに適用できると思います。このパターンを使用すると、getFamilyID()のようなメソッドを階層に追加することを回避し、コンパイラーにディスパッチを任せることができます。if (dynamic_cast<> )この方法では、多くの種類の条件付きロジックをコードに入れる必要はありません。

于 2011-06-07T14:02:38.190 に答える