0

这是一个基本的 OO 设计问题。我正在用 C++ 编写类,以根据已解析的输入 C 文件来表示流程图中的项目。

简单地说,我们有 2 种类型的项目(类):FlowChartActionItemFlowChartConditionItem。它们分别代表流程图的动作和决策/条件元素。它们还分别表示存在于输入 C 文件中的语句和 If 条件。这两个类都继承了 FlowChartItem

每个子类都有许多指向它们后面的项目的指针;是的,我们有一个图表,带有节点(项目)和链接(指针)。但是FlowChartActionItem只有一个外向指针,而FlowChartConditionItem有 3 个外向指针(对于 then-statements 分支,else-statements 分支和指向 if 条件两个分支之后的任何内容的指针。

我的问题是为外向指针(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 index 来告诉设置器要设置哪个指针。但我不喜欢这样,我需要让事情变得更整洁。因为代码将不可读,例如:

item1.setNextItem(item2,1);

我们不记得 1 是什么意思?当时的分支?别的???

显而易见的答案是在 FlowCharItem 中定义一个枚举,但是我们将遇到以下两个问题之一:

1- 现在将定义枚举值,因此将为当前子类 FlowChartActioItem 和 FlowChartConditionItem 量身定制,因此对未来子类的 SetNextItem 调用将具有非常差的可读性。更糟糕的是,他们不能有超过 3 个向外的指针!

2-通过让未来子类的开发人员编辑 FlowChartItem 的头文件并在枚举中添加任何值来解决第一个问题!当然不能接受!

我有什么解决方案来保持 - 良好的可读性 - 我的课程的整洁可扩展性?

4

3 回答 3

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 回答
1

这是一种常见的架构困境。不同的子类具有略微不同的共享行为,您需要以某种有意义的方式将共同本质提取到基类中。您通常会后悔的一个陷阱是让子类功能渗入父类。例如,我不会为 FlowChartItem 中定义的输出连接类型推荐一组潜在的枚举名称。这些名称仅在使用它们的单个子节点中才有意义。使每个子类复杂化以适应其兄弟姐妹的设计同样糟糕。最重要的是,亲!保持。它。简单的。

在这种情况下,感觉就像你想多了。围绕它代表什么以及它将如何被其他代码使用的抽象概念设计您的父类,而不是它的继承者将如何专门化它。

可以更改名称 SetNextItem 以更清楚地说明这两个参数的作用。它只是整个图表意义上的“下一个”项目,而不是单个 FlowChartItem 的上下文。流程图是有向图,每个节点通常只知道自己及其连接。(另外,你不是在写 Visual Basic,所以容器索引从 0 开始!:-))

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

或者,如果您更喜欢较短的名称,那么您可以设置“N'th”输出项: SetNthOutItem.

由于使用超出范围的索引设置子级无效,因此您可能希望在 FlowChartItem 中有另一个纯虚函数,它返回支持的最大子级数并使 SetChildByIndex 返回成功/失败代码(或者如果您'是这些人中的一员,抛出异常)如果索引超出范围。

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

现在......写完所有这些后,我开始想知道您拥有的将调用此函数的代码。它真的只知道作为 FlowChartItem 的每个节点,但仍然需要以它不知道其重要性的特定顺序设置它的子节点吗?如果您有其他代码知道实际项目类型及其子订单的含义,并且该代码将项目指针及其索引号提供给执行设置的代码,这可能是有效的。也许是反序列化代码,但这不是处理序列化的正确方法。FlowChartItem 是否通过严格的 API 公开,并且图表是由知道不同类型的流程图项但无法访问实际类的代码构建的?在那种情况下可能有效,但我现在推测的远远超出了你的细节'

但是,如果这个函数只会被知道真实项目类型、可以访问实际类并且知道索引含义的代码调用,那么它可能根本不应该在基类中。

但是,我可以想象有很多类型的代码需要按顺序获取 FlowChartItem 的子项,但不知道该顺序的重要性。绘制流程图的代码,执行流程图的代码,等等。如果您为简洁起见减少您的问题并且也在考虑类似的 getter 方法,那么上述建议将适用(尽管您也可以考虑使用迭代器模式)。

于 2012-09-03T16:50:03.580 回答
0

我需要一个不依赖于子类拥有的指针数量的通用设置器。

拥有像这样的可变结构的唯一方法是允许客户端访问数据结构,比如说,std::vector<FlowChartItem*>std::unordered_map<unsigned int, FlowChartItem*>其他。他们可以读取它并设置值。

从根本上说,只要您尝试动态设置静态项目,就会一团糟。您正在尝试实现自己的高度原始的反射系统。

如果您希望在没有语言内置反射系统的情况下动态设置它们,或者无休止地浪费您的生命来尝试使其工作,那么您需要拥有动态项目。

作为奖励,如果你有类似的东西,你的派生类的用例就会少很多,你甚至可以摆脱它们。WinRAR™。

于 2012-09-03T15:49:46.833 回答