pau*_*l23 10 c++ paradigms refactoring
很长一段时间我一直在研究应用程序.由于编程只是一个爱好,这个项目已经花了太长时间,但除此之外.我现在处于一个每个"问题"变得非常难以解决的地步.我正在考虑重构代码,但这会导致"完全"重写.
让我解释一下这个问题,以及我目前如何解决它.基本上我有数据,我让事情发生在这些数据上(我描述的每个程序都不是我吗?).会发生什么:
数据 - >要求查看器显示 - >查看器显示基于实际数据查看器的数据返回用户输入 - >数据 - >要求"执行程序"执行它 - >新数据
现在这曾经很好地工作,我原本以为"嘿,我可以通过qt或Windows更改命令提示符 - 或者甚至采用外部(C#)并简单地调用此程序".
然而随着计划的发展,它变得越来越无聊.最重要的是,数据以不同的方式显示,具体取决于数据是什么,更重要的是 - 它位于何处.所以我回到了树上并添加了一些"跟踪"父线是什么".然后普通观众将搜索最具体的实际小部件.它使用了一个带有[location; widget]值的列表,并且找到了最匹配的位置.
问题在更新新"数据"时开始 - 我必须经历所有资产 - 查看器,保护程序等等.更新检查机制给了我很多错误.像"嘿,为什么它显示错误的小部件"现在又来了?"
现在我可以完全交换它.而不是调用通用查看器的树数据结构.我会使用OO"内部"树功能.节点将是孩子(当需要新的观察者或保存机制时,形成新的孩子).
这将删除困难的检查机制,我检查树中的位置.然而,它可能会打开另外一堆蠕虫.我想对此发表一些意见吗?我应该让观众完全分开 - 难以检查数据吗?或者新方法更好,但它将数据和执行结合到一个节点中.(所以,如果我想从qt改为cli/C#,那几乎是不可能的)
我到底应该采用什么方法?还有什么我可以做的吗?为了使查看器保持独立,还要防止必须检查以查看应显示哪个小部件?
编辑,只是为了显示一些"代码"以及我的程序如何工作.不知道这是否有任何好处,正如我所说,它已成为一种非常集中的方法论.
它意味着将几个"游戏制作者项目"合并在一起(因为GM:工作室奇怪地缺乏这个功能).Gamemaker项目文件只是xml文件的集合.(仅包含指向其他xml文件的链接的主xml文件,以及每个资源的对象,子画面,声音,房间等的xml文件).然而,有一些"怪癖"使得它不可能用诸如boost属性树或qt之类的东西来阅读:1)属性/子节点的顺序在文件的某些部分非常重要.2)白色空间经常被忽略,但在其他方面,保留它是非常重要的.
话虽如此,节点也完全相同也有很多点.就像背景可以拥有的那样<width>200</width>
,房间也可以拥有它.然而对于用户而言,他所谈论的宽度非常重要.
无论如何,所以"general viewer"(AskGUIFn)有以下typedef来处理这个问题:
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;
Run Code Online (Sandbox Code Playgroud)
在"GMProject :: pTree"是树节点的情况下,RESOURCE_TYPES是一个常量,用于跟踪我现在处于什么样的资源(精灵,对象等)."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.
Run Code Online (Sandbox Code Playgroud)
然后,当树数据结构告诉普通观众希望显示它时,查看器执行以下功能:
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;
}
Run Code Online (Sandbox Code Playgroud)
这就是问题开始坦率的地方.该CallStackSerialize()
函数依赖于调用堆栈.但是,call_stack存储在"处理程序"中.我把它存储在那里,因为一切都从处理程序开始.我不确定我应该在哪里存储这个"call_stack".引入另一个跟踪正在发生的事情的对象?我尝试使用节点本身存储父节点的路径.(防止需要调用堆栈).然而,这并不像我希望的那样好:每个节点只有一个包含其子节点的向量.所以使用指针是不可能的,指向父笔记......(PS:也许我应该在另一个问题中对此进行改革..)
将查看器中的这种复杂的位置检查机制重构/重写为专用类是有意义的,因此您可以改进解决方案而不影响程序的其余部分。我们就这样称呼吧NodeToWidgetMap
。
架构
似乎您正在走向模型-视图-控制器架构,在我看来这是一件好事。您的树结构及其节点是模型,其中查看器和“小部件”是视图,并且根据节点选择小部件的逻辑将是控制器的一部分。
主要问题仍然是何时以及如何为给定节点N选择小部件 w N 以及如何存储此选择。
NodeToWidgetMap:何时选择
如果您可以假设即使节点移动,w N在其生命周期内也不会改变,那么您可以在创建节点时正确选择它。否则,您需要知道位置(或通过 XML 的路径),从而在请求节点时找到节点的父节点。
查找父节点
我的解决方案是存储指向而不是节点实例本身的指针,也许使用boost::shared_ptr
. 这有缺点,例如复制节点迫使您实现自己的复制构造函数,该构造函数使用递归来创建子树的深层复制。(但是移动不会影响子节点。)
存在替代方案,例如每当触及父节点(相应的祖父向量)时保持子节点更新。或者,您可以定义一个Node::findParentOf(node)
函数,知道某些节点只能(或经常)作为某些节点的子节点找到。这虽然很残酷,但对于小树来说效果相当好,只是扩展性不太好。
NodeToWidgetMap:如何选择尝试在纸上写下如何选择 w N 的
规则,也许只是部分。然后尝试将这些规则翻译成C++。就代码而言,这可能会稍长一些,但会更容易理解和维护。
您当前的方法是使用正则表达式来匹配 XML 路径(堆栈)。
我的想法是创建一个查找图,其边缘由 XML 元素名称标记,其节点指示应使用哪个小部件。这样,您的 XML 路径(堆栈)就描述了穿过图表的路线。那么问题就变成了是否显式地对图进行建模,或者是否可以使用一组函数调用来镜像该图。
NodeToWidgetMap:存储选择的位置
将唯一的数字 ID 与每个节点相关联,使用 NodeToWidgetMap 内从节点 id 到小部件的映射来记录小部件选择。
重写与重构
如果您重写,您可能会获得与 Qt 等现有框架的良好结合,以便专注于您的程序而不是重写轮子。将编写良好的程序从一个框架移植到另一个框架比抽象每个平台的特性要容易得多。Qt 是一个很好的框架,可以帮助您积累经验并很好地理解 MVC 架构。
完全重写让您有机会重新思考一切,但也意味着您需要从头开始,并且在很长一段时间内没有新版本的风险。谁知道你是否有足够的时间来完成?如果您选择重构现有结构,您将逐步改进它,每一步后都会有一个可用的版本。但陷入旧思维方式的风险很小,因为重写几乎迫使你重新思考一切。所以这两种方法都有其优点,如果你喜欢编程我会重写。更多编程,更多快乐。