0

これは基本的な OO 設計の問題です。解析された入力 C ファイルに従ってフロー チャートの項目を表すクラスを C++ で作成しています。

FlowChartActionItemFlowChartConditionItemの 2 種類の項目 (クラス) があります。これらは、それぞれフローチャートのアクションおよび決定/条件要素を表します。また、入力 C ファイルに存在するステートメントと If 条件もそれぞれ表します。どちらのクラスもFlowChartItemを継承しています。

各サブクラスには、その後に続く項目への多数のポインターがあります。はい、ノード (アイテム) とリンク (ポインター) を含むグラフがあります。ただし、FlowChartActionItemには 1 つの外向きポインターしかありませんが、FlowChartConditionItemには 3 つの外向きポインターがあります (then-statements ブランチ、else-statements ブランチ、および if-condition の両方のブランチの後に来るものへのポインター)。

私の問題は、外向きのポインター (nextItems) の適切なセッターを作成することです。クラスを見てください:

class FlowChartItem
{
public:
    //I **need** this setter to stay in the parent class FlowChartItem
    virtual void SetNextItem(FlowChartItem* nextItem, char index) = NULL;
};

-

class FlowChartActionItem:public FlowChartItem
{
public:
    FlowChartItem* nextItem; //Only 1 next item
public: 
    void SetNextItem(FlowChartItem* nextItem, char index);
};

-

class FlowChartConditionItem: public FlowChartItem
{
public:
    FlowChartItem* nextItem;
    FlowChartItem* trueBranchItem;
    FlowChartItem* falseBranchItem; //we have 3 next items here
public:
    void SetNextItem(FlowChartItem* nextItem, char index);
};

サブクラスが持っているポインターの数に依存しない汎用セッターが必要でした。ご覧のとおり、どのポインターを設定するかをセッターに伝えるために char インデックスを使用しました。しかし、私はこれが好きではなく、物事をきちんとする必要があります. コードが読めないため、例えば:

item1.setNextItem(item2,1);

1が何を意味するか覚えていませんか?then-ブランチ ? 他に???

明らかな答えは、FlowCharItem で列挙型を定義することですが、次の 2 つの問題のいずれかが発生します。

1- Enum 値が定義され、現在のサブクラス FlowChartActioItem および FlowChartConditionItem に合わせて調整されるため、将来のサブクラスでの SetNextItem の呼び出しは非常に読みにくくなります。さらに悪いことに、3 つ以上の外向きポインターを持つことはできません。

2- 将来のサブクラスの開発者に FlowChartItem のヘッダー ファイルを編集させ、enum に任意の値を追加して、最初の問題を解決します。もちろん受け付けません!

クラスの読みやすさを維持するためにどのような解決策がありますか?

4

3 に答える 3

1

これは、一般的なアーキテクチャのジレンマの一形態です。子クラスが異なれば、動作も少し異なります。意味のある方法で、基本クラスに共通のエッセンスを抽出する必要があります。通常、後悔する罠は、子クラスの機能を親クラスにブリードさせることです。たとえば、FlowChartItemで定義された出力接続のタイプの潜在的な列挙型名のセットはお勧めしません。これらの名前は、それらを使用する個々の子ノードでのみ意味があります。兄弟の設計に対応するために各サブクラスを複雑にすることも同様に悪いことです。何より、KIS!保つ。それ。単純。

この場合、あなたはそれを考えすぎているように感じます。親クラスは、継承者がどのように特殊化するかではなく、それが表すものと他のコードでどのように使用されるかという抽象的な概念に基づいて設計します。

SetNextItemという名前を変更するだけで、両方のパラメーターの機能をより明確にすることができます。これは、単一のFlowChartItemのコンテキストではなく、チャート全体の意味での「次の」アイテムにすぎません。フローチャートは有向グラフであり、各ノードは通常、それ自体とその接続についてのみ認識します。(また、ビジュアルベーシックを記述していないため、コンテナーのインデックスは0から始まります!:-))

virtual void SetOutConnectionByIndex(FlowChartItem* nextItem, char index);

または、短い名前を使用する場合は、「N番目」の出力項目を設定できます SetNthOutItem

範囲外のインデックスを使用して子を設定することは無効であるため、サポートされている子の最大数を返し、SetChildByIndexに成功/失敗コードを返すようにする別の純粋仮想関数をFlowChartItemに含める必要があります(または'それらの人々の1人であり、例外をスローします)インデックスが範囲外の場合。

virtual bool SetChildByIndex(FlowChartItem* item, char index);

さて...すべてを書いたので、この関数を呼び出すコードについて疑問に思い始めます。実際には、各ノードをFlowChartItemとしてのみ認識していますが、その子を特定の順序で設定する必要がありますが、その重要性はわかりません。これは、実際のアイテムタイプとその子の順序の意味を認識している他のコードがあり、そのコードが設定を行うコードにアイテムポインターとそのインデックス番号を提供している場合に有効です。おそらく逆シリアル化コードですが、これはシリアル化を処理する正しい方法ではありません。FlowChartItemは厳密なAPIを介して公開されており、チャートはさまざまなタイプのフローチャートアイテムを認識しているが、実際のクラスにはアクセスできないコードによって作成されていますか?その場合はおそらく有効ですが、私は今あなたの詳細をはるかに超えていると推測しています

ただし、この関数が実際のアイテムタイプを認識し、実際のクラスにアクセスでき、インデックスの意味を認識しているコードによってのみ呼び出される場合、これはおそらく基本クラスに含まれるべきではありません。

ただし、その順序の重要性を知らなくても、FlowChartItemの子を順番にフェッチする必要がある多くの種類のコードを想像することができます。フローチャートを描くためのコード、フローチャートを実行するためのコードなど。質問を簡潔にするために切り詰めて、同様のゲッターメソッドについても考えている場合は、上記のアドバイスが適用されます(ただし、イテレーターパターンを検討することもできます)。

于 2012-09-03T16:50:03.580 に答える
1

基本クラスの「汎用」 SetNextItem に対する疑わしい必要性を回避し、アイデアを実装できる方法を提案します。

アイテムを(隣接マップと呼んでいます) に格納し、アイテムを名前で設定できますこのように、サブクラスは必要な数の隣接関係を持つことができ、隣接タイプの中央の列挙を維持する必要はありません。FlowChartItem*std::map<std::string, FlowChartItems*>

class FlowChartItem
{
public:
    virtual void SetAdjacency(FlowChartItem* item, const std::string &type)
    {
        // Enforce the use of a valid adjacency name
        assert(NameSet().count(type) != 0);

        adjacencyMap_[name] = nextItem
    }

protected:
    // Subclasses must override this and return a set of valid adjacency names
    const std::set<std::string>& NameSet() = 0;

    std::map<std::string, FlowChartItem*> adjacencyMap_;
};

class FlowChartActionItem : public FlowChartItem
{
public:
    // Convenience member function for when we're dealing directly
    // with a FlowChartActionItem.
    void SetNextItem(FlowChartItem* item) {SetAdjacency(item, "next");}

protected:
    const std::set<std::string>& NameSet()
    {
        // Initialize static nameSet_ if emtpy
        return nameSet_;
    }

private:
    // One set for the whole class (static).
    const static std::set<std::string> nameSet_;

    static std::set<std::string> MakeNameSet()
    {
        std::set<std::string> names;
        names.insert("next");
        return names;
    }
}

// Initialize static member
const std::set<std::string> FlowChartActionItem::nameSet_ =
    FlowChartActionItem::MakeNameSet();

使用法:

item1.SetAdjacency(&item2, "next");
于 2012-09-03T15:17:59.507 に答える
0

サブクラスが持っているポインターの数に依存しない汎用セッターが必要でした。

このような変更可能な構造を持つ唯一の方法は、クライアントがデータ構造などにアクセスできるようにすることstd::vector<FlowChartItem*>ですstd::unordered_map<unsigned int, FlowChartItem*>。彼らはそれを読んで値を設定できます。

基本的に、静的アイテムを動的に設定しようとしている限り、混乱することになります。独自の非常に原始的なリフレクション システムを実装しようとしています。

言語に組み込まれているリフレクション システムを使用せずに動的に設定したり、それを機能させようとして延々と無駄遣いしたりする必要がある場合は、動的な項目が必要です。

おまけとして、そのようなものがある場合、派生クラスのユースケースはかなり低くなり、おそらくそれらを取り除くことさえできます. WinRAR™.

于 2012-09-03T15:49:46.833 に答える