c++ - 编程范式;想知道是否需要重写/重构

标签 c++ refactoring paradigms

很长一段时间以来,我一直在研究一个应用程序。由于编程只是一种爱好,这个项目已经花费了太长时间,但这不是重点。我现在正处于每个“问题”都变得非常难以解决的地步。我正在考虑重构代码,但这会导致“完全”重写。

让我解释一下这个问题,以及我目前是如何解决它的。基本上我有数据,我让事情发生在这些数据上(我描述了每个程序,不是吗?)。发生的事情是:

Data -> asks viewer to display -> viewer displays data based on actual data viewer returns user input -> data -> asks "executor" to execute it -> new data



enter image description here

现在这曾经很好用,我最初在想“嘿,我可以例如通过 qt 或 windows 更改命令提示符 - 甚至使用外部(C#)并简单地调用这个程序”。

然而,随着程序的增长,它变得越来越令人厌烦。最重要的是,数据以不同的方式显示,具体取决于数据是什么,更重要的是,它位于何处。所以我回到树上并添加一些方法来“跟踪”父行是什么“。然后一般查看者将搜索最具体的实际小部件。
它使用了一个带有 [location; 的列表; widget] 值,并找到最佳匹配位置。

当更新新的“数据”时问题就开始了——我必须检查所有的 Assets ——查看器、保护程序等。更新检查机制给了我很多错误......诸如“嘿,为什么它显示错误的小部件之类的东西”现在又来了?”。

现在我可以完全交换这个了。而不是调用通用查看器的树数据结构。我会使用面向对象的“内部”树功能。节点将是子节点(& 当需要新的查看器或保存机制时,会形成新的子节点)。

这将消除困难的检查机制,在那里我检查树中的位置。然而,它可能会打开一整 jar 蠕虫。
我想就此发表一些意见?我应该让查看器完全分开 - 检查数据有困难吗?或者新方法更好,但它将数据和执行结合到一个节点中。 (因此,如果我想从 qt 更改为 cli/C#,这几乎是不可能的)

enter image description here

我最终应该采用什么方法?还有什么我可以做的吗?保持查看器分开,但又避免必须检查以查看应该显示什么小部件?

编辑,只是为了显示一些“代码”以及我的程序如何工作。不确定这是否像我所说的那样有好处,它已经成为一堆方法论。

它旨在将几个“游戏制作者项目”合并在一起(因为 GM:studio 奇怪地缺乏该功能)。 Gamemaker 项目文件只是一组 xml 文件。 (主 xml 文件仅包含指向其他 xml 文件的链接,以及每个资源的 xml 文件 - 对象、 Sprite 、声音、房间等 -)。然而,有一些“怪癖”使得使用 boost 属性树或 qt 之类的东西无法真正读取:1)属性/子节点的顺序在文件的某些部分非常重要。 2) 空白经常被忽略,但在其他地方保留它是非常重要的。

话虽如此,也有很多节点完全相同的点..就像背景可以有<width>200</width>一样。一个房间也可以有。然而对于用户来说,他所谈论的宽度非常重要。

无论如何,因此“一般查看器”(AskGUIFn)具有以下类型定义来处理此问题:
    typedef int (AskGUIFn::*MemberFn)(const GMProject::pTree& tOut, const GMProject::pTree& tIn, int) const;
    typedef std::vector<std::pair<boost::regex, MemberFn> > DisplaySubMap_Ty;
    typedef std::map<RESOURCE_TYPES, std::pair<DisplaySubMap_Ty, MemberFn> > DisplayMap_Ty;

其中 "GMProject::pTree"是一个树节点,RESOURCE_TYPES 是一个常量,用于跟踪我目前的资源类型( Sprite 、对象等)。 “memberFn”在这里只是加载小部件的东西。 (当然,尽管 AskGUIFn 不是唯一的通用查看器,但只有在其他“自动”覆盖、跳过、重命名处理程序失败时才会打开此查看器)。

现在展示这些映射是如何初始化的(命名空间“MW”中的所有内容都是一个 qt 小部件):
AskGUIFn::DisplayMap_Ty AskGUIFn::DisplayFunctionMap_INIT() {
    DisplayMap_Ty t;
        DisplaySubMap_Ty tmp;

        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^instances "), &AskGUIFn::ExecuteFn<MW::RoomInstanceDialog>));
        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^code $"), &AskGUIFn::ExecuteFn<MW::RoomStringDialog>));
        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^(isometric|persistent|showcolour|enableViews|clearViewBackground) $"), &AskGUIFn::ExecuteFn<MW::ResourceBoolDialog>));
        //etc etc etc
    t[RT_ROOM] = std::pair<DisplaySubMap_Ty, MemberFn> (tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>);

        tmp.clear();
        //repeat above
    t[RT_SPRITE] = std::pair<DisplaySubMap_Ty, MemberFn>(tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>);
    //for each resource type.

然后当树数据结构告诉普通查看器它希望被显示时,查看器执行以下函数:
AskGUIFn::MemberFn AskGUIFn::FindFirstMatch() const {
    auto map_loc(DisplayFunctionMap.find(res_type));
    if (map_loc != DisplayFunctionMap.end()) {
        std::string stack(CallStackSerialize());
        for (auto iter(map_loc->second.first.begin()); iter != map_loc->second.first.end(); ++iter) {
            if (boost::regex_search(stack, iter->first)) {
                return iter->second;
            }
        }
        return map_loc->second.second;
    }

    return BackupScreen;
}

这就是问题开始变得坦率的地方。 CallStackSerialize()函数依赖于调用堆栈。但是,call_stack 存储在“处理程序”中。我将它存储在那里,因为一切都从处理程序开始。我不确定我应该在哪里存储这个“call_stack”。引入另一个跟踪正在发生的事情的对象?
我尝试使用节点本身存储父节点的路径。 (防止需要调用堆栈)。然而,这并不像我希望的那样顺利:每个节点只有一个包含其子节点的 vector 。所以使用指针指向父注释是不可能的......
(PS:也许我应该在另一个问题中对此进行改革..)

最佳答案

将查看器中这种复杂的位置检查机制重构/重写为专用类是有意义的,因此您可以改进您的解决方案,而不会影响程序的其余部分。让我们称之为 NodeToWidgetMap .

建筑
看来您正朝着 Model-View-Controller 前进架构,这是 IMO 的一件好事。您的树结构及其节点是模型,其中查看器和“小部件”是 View ,而根据节点选择小部件的逻辑将是 Controller 的一部分。

主要问题仍然是您何时以及如何为给定节点 N 选择小部件 wN 以及如何存储此选择。

NodeToWidgetMap:何时选择
如果您可以假设 wN 在其生命周期内即使节点被移动也不会改变,那么您可以在创建节点时正确选择它。否则,您将需要知道位置(或通过 XML 的路径),从而在请求节点时找到节点的父节点。

查找父节点
我的解决方案是存储指向而不是节点实例本身的指针,也许使用 boost::shared_ptr .这有缺点,例如复制节点迫使您实现自己的复制构造函数,该构造函数使用递归来创建子树的深层拷贝。 (但是移动不会影响子节点。)

存在替代方案,例如每当接触祖父 vector 各自的父节点时,保持子节点是最新的。或者你可以定义一个 Node::findParentOf(node)函数知道某些节点只能(或经常)被发现作为某些节点的子节点。这很粗暴,但对于小树来说效果很好,只是不能很好地扩展。

NodeToWidgetMap:如何选择
试着在一张纸上写下如何选择 wN 的规则,也许只是部分地写下来。然后尝试将这些规则翻译成 C++。这在代码方面可能会稍长一些,但会更容易理解和维护。

您当前的方法是使用正则表达式来匹配 XML 路径(堆栈)。

我的想法是创建一个查找图,其边由 XML 元素名称标记,其节点指示应使用哪个小部件。通过这种方式,您的 XML 路径(堆栈)描述了通过图形的路线。那么问题就变成了是否显式地对图建模,或者是否可以使用一组函数调用来反射(reflect)该图。

NodeToWidgetMap:存储选择的位置
将唯一的数字 id 与每个节点相关联,使用 NodeToWidgetMap 中从节点 id 到小部件的映射来记录小部件选择。

重写与重构
如果您重写,您可能会获得与 Qt 等现有框架相关联的良好杠杆作用,以便专注于您的程序而不是重写轮子。将编写良好的程序从一个框架移植到另一个框架比抽象每个平台的特性要容易得多。 Qt 是一个很好的框架,用于获得经验和对 MVC 架构的良好理解。

完全重写让您有机会重新思考所有事情,但也意味着您会面临从头开始并且在很长一段时间内没有新版本的风险。谁知道你是否有足够的时间来完成?如果您选择重构现有结构,您将逐步改进它,在每一步之后都有一个可用的版本。但是,被困在旧的思维方式中的风险很小,因为重写几乎迫使你重新思考一切。所以这两种方法都有其优点,如果你喜欢编程,我会重写。更多的编程,更多的乐趣。

关于c++ - 编程范式;想知道是否需要重写/重构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13396589/

相关文章:

oop - OOP 的重大挑战者

oop - 怎么可能有一个纯面向对象的语言呢?

c++ - 将 cx_vec 元素转换为双复杂 Armadillo C++

c++ - 使用样式表更改索引 QTabBar 选项卡的背景

c# - 将字节数组从 C++ DLL 返回到 C#

c++ - 如何从源代码中禁用 std::clog 日志记录?

git - 在 IntelliJ 中重构时如何将更改日志保存在 git 文件中?

java - 具有多个方法对象的测试类

oop - 函数式编程与面向对象编程

javascript - 有没有办法重构这个 javascript/jquery?