如何在我的代码中避免“经理”


26

我目前正在为C ++ 重新设计我的实体系统,并且我有很多经理。在我的设计中,我有这些类,以便将我的库联系在一起。关于“经理”类,我听说过很多坏事,也许我没有适当地命名我的类。但是,我不知道还有什么名字。

在我的图书馆中,大多数经理都是由以下类组成的(尽管确实有所不同):

  • 容器-管理器中对象的容器
  • 属性-管理器中对象的属性

在我的图书馆新设计中,我有这些特定的类,以便将我的图书馆联系在一起。

  • ComponentManager-管理实体系统中的组件

    • 组件容器
    • ComponentAttributes
    • 场景*-对场景的引用(请参见下文)
  • SystemManager-管理实体系统中的系统

    • 系统容器
    • 场景*-对场景的引用(请参见下文)
  • EntityManager-管理实体系统中的实体

    • EntityPool-实体池
    • EntityAttributes-实体的属性(只能由ComponentContainer和System类访问)
    • 场景*-对场景的引用(请参见下文)
  • 场景-将所有经理联系在一起

    • 组件管理器
    • 系统管理员
    • 实体管理器

我当时只是想将所有容器/池放入Scene类本身。

代替这个:

Scene scene; // create a Scene

// NOTE:
// I technically could wrap this line in a createEntity() call in the Scene class
Entity entity = scene.getEntityManager().getPool().create();

就是这样的:

Scene scene; // create a Scene

Entity entity = scene.getEntityPool().create();

但是,我不确定。如果要进行后者,那意味着我将在Scene类中声明许多对象和方法。

笔记:

  1. 实体系统只是用于游戏的设计。它由3个主要部分组成:组件,实体和系统。这些组件只是简单的数据,可以将这些数据“添加”到实体中,以使这些实体与众不同。实体由整数表示。系统包含具有特定组件的实体逻辑。
  2. 之所以要更改我的库的设计,是因为我认为它可以进行很多更改,目前我不喜欢它的感觉/流程。

您能详细说明一下您听说过的有关经理的坏事以及它们与您的关系吗?
MrFox


@MrFox基本上我听过Dunk提到的内容,并且我的库中有很多* Manager类,我想摆脱它们。
miguel.martin

这也可能会有所帮助:medium.com/@wrong.about/…–
Zapadlo

您可以从可用的应用程序中考虑有用的框架。为想象中的应用程序编写有用的框架几乎是不可能的。
凯文·克莱恩

Answers:


19

DeadMG在您的代码细节上很专一,但是我觉得它缺少澄清。同样,我也不同意他的一些建议,这些建议在某些特定情况下(例如大多数高性能视频游戏开发)并不成立。但是他是全球正确的,您的大多数代码现在都没有用。

正如Dunk所说,Manager类之所以这样称呼是因为它们“管理”事物。“管理”就像“数据”或“做”一样,它是一个抽象词,几乎可以包含任何内容。

大约7年前,我几乎和您在同一地方,并且我开始认为我的思维方式有问题,因为要付出很多努力才能编写出无所事事的代码。

为了解决此问题,我所做的更改是更改了我在代码中使用的词汇表。我完全避免使用通用词(除非它是通用代码,但是在不创建通用库时很少见)。我避免将任何类型命名为“经理”或“对象”。

影响直接体现在代码中:它迫使您找到与您的实际责任相对应的正确单词。如果您觉得该类型可以完成多项工作(保留Book的索引,保持它们的生存状态,创建/销毁书籍),那么您需要使用不同的类型,每种类型都有责任,然后将它们合并到使用它们的代码中。有时候我需要工厂,有时候不需要。有时我需要一个注册表,所以我设置了一个注册表(使用标准容器和智能指针)。有时候,我需要一个由几个不同子系统组成的系统,因此我会尽可能地分离所有东西,以便每个部分都能做一些有用的事情。

绝对不要将类型命名为“经理”,并且确保我的所有类型都具有一个唯一的角色。有时很难找到名称,但这是一般编程中最难的事情之一


如果一对夫妇dynamic_cast正在破坏您的性能,请使用静态类型系统-这就是它的作用。必须像LLVM一样滚动自己的RTTI,这实际上是一种特殊的情况,而不是一般的情况。即使那样,他们也不这样做。
DeadMG

@DeadMG我并不是特别在谈论这部分,但是大多数高性能游戏负担不起,例如,通过新的甚至是其他通常适合编程的内容进行动态分配。它们是特定硬件的特定野兽,您无法一概而论,这就是为什么“取决于”是更普遍的答案的原因,尤其是对于C ++。(顺便说一句,游戏开发人员中没有人使用动态类型转换,除非有插件,否则它就永远不会有用,即使那样也很少见)。
Klaim

@DeadMG我没有使用dynamic_cast,也没有使用dynamic_cast的替代方法。我在库中实现了它,但我不厌其烦地将其取出。template <typename T> class Class { ... };实际上是为不同的类(即自定义组件和自定义系统)分配ID。ID分配用于将组件/系统存储在矢量中,因为地图可能无法带来游戏所需的性能。
miguel.martin

好吧,我实际上也没有实现dynamic_cast的替代方案,只是伪造的type_id。但是,我仍然不想要type_id实现什么。
miguel.martin

“找到与您的类型的真正责任相对应的正确单词”-尽管我完全同意这一点,但是开发人员社区通常就某一特定类型的责任没有一个单词(如果有)。
bytedev 2014年

23

好吧,我通读了您链接到的一些代码以及您的帖子,而我的诚实总结是,其中的大多数基本上完全一文不值。抱歉。我的意思是,您已经拥有所有这些代码,但是您什么都还没完成。完全没有 我将不得不在这里进行一些深入研究,所以请耐心等待。

让我们从ObjectFactory开始。首先,函数指针。这些是无状态的,创建对象的唯一有用的无状态方法是new并且您不需要工厂。有状态地创建对象是有用的,而函数指针对于此任务则无用。其次,每个对象都需要知道如何销毁自身,而不必找到确切的创建实例来销毁它。此外,为了您的用户的异常和资源安全,您必须返回一个智能指针,而不是原始指针。您的整个ClassRegistryData类是std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>,但有上述漏洞。它根本不需要存在。

然后,我们有了AllocatorFunctor-已经提供了标准的分配器接口-更不用说它们只是包装器,delete obj;并且return new T;-再次提供了它们,它们将不会与存在的任何有状态或自定义内存分配器进行交互。

而且,ClassRegistry很简单,std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>但是您为它编写了一个类,而不是使用现有功能。相同,但是带有一堆糟糕的宏的全局完全可变状态。

我在这里要说的是,您已经创建了一些类,可以用从Standard类构建的功能替换整个事物,然后通过使它们变为全局可变状态并涉及一堆宏来使它们更糟。您可能会做的最坏的事情。

然后我们有Identifiable。这里有很多相同的故事- std::type_info已经完成了将每种类型标识为运行时值的工作,并且不需要用户过多的重复。

    template<typename T, typename Y>
    bool isSameType(const X& x, const Y& y) { return typeid(x) == typeid(y); }
    template <class Type, class T>
    bool isType(const T& obj)
    {
            return dynamic_cast<const Type*>(std::addressof(obj));
    }

问题解决了,但是没有前面的任何两百行宏。这也将您的IdentifiableArray标记为std::vector空,但是即使唯一所有权或非所有权会更好,您也必须使用引用计数。

哦,我是否提到类型包含一堆绝对没用的 typedef?与直接使用原始类型相比,您实际上没有任何优势,并且会损失很多可读性。

接下来是ComponentContainer。您不能选择所有权,不能为各个组件的派生类提供单独的容器,也不能使用自己的生命周期语义。删除后悔。

现在,如果您进行了大量重构,则ComponentFilter实际上可能会有所值。就像是

class ComponentFilter {
    std::unordered_map<std::type_info, unsigned> types;
public:
    template<typename T> void SetTypeRequirement(unsigned count = 1) {
        types[typeid(T)] = count;
    }
    void clear() { types.clear(); }
    template<typename Iterator> bool matches(Iterator begin, Iterator end) {
        std::for_each(begin, end, [this](const std::iterator_traits<Iterator>::value_type& t) {
            types[typeid(t)]--;
        });
        return types.empty();
    }
};

这不会处理从您尝试处理的类派生的类,但您的类也不会处理。

其余的几乎都是相同的故事。如果没有使用您的库,这里没有什么比这更好。

您如何避免在此代码中使用管理器?基本上删除所有。


14
我花了一些时间来学习,但是最可靠的,没有错误的代码是不存在的代码。
Tacroy

1
我不使用std :: type_info的原因是因为我试图避免 RTTI。我提到了该框架针对游戏,这基本上是我不使用typeidor 的原因dynamic_cast。不过,感谢您的反馈,我非常感谢。
miguel.martin

这个批评家是基于您对组件实体系统游戏引擎的了解还是对C ++的一般代码审查?
2013年

1
@Den我认为设计问题比什么都重要。
miguel.martin

10

Manager类的问题是经理可以做任何事情。您无法阅读课程名称,也不知道课程的用途。EntityManager ....我知道它对实体有作用,但是谁知道。SystemManager ...是的,它管理系统。这很有帮助。

我的猜测是,您的经理班级可能会做很多事情。我敢打赌,如果您将这些东西分割成紧密相关的功能块,那么您可能能够提出与功能块相关的类名,任何人都可以通过查看类名来告诉他们做什么。这将使维护和理解设计更加容易。


1
他们不仅可以+1,而且可以在足够长的时间范围内做任何事情。人们将“经理”描述符视为创建厨房水槽/瑞士军刀类的邀请。
Erik Dietrich 2013年

@Erik-嗯,是的,我应该补充一点,拥有一个好的班级名称不仅重要,因为它告诉别人班级可以做什么;但同样,班级名称也说明了班级不应该做什么。
Dunk

3

Manager耸耸肩,我实际上并不认为在这种情况下如此糟糕。如果有一个我认为Manager可以原谅的地方,那就是实体组件系统,因为它实际上是在“ 管理 ”组件,实体和系统的生命周期。至少,这是一个比之有意义的名称,Factory因为在这种情况下,ECS确实比创建东西做得更多。

我想你可以Database这样用ComponentDatabase。或者,您可能只使用ComponentsSystemsEntities(这是我个人使用的,主要是因为我喜欢简洁的标识符,尽管它所传递的信息可能比“经理”少得多)。

我觉得有点笨拙的部分是这样的:

Entity entity = scene.getEntityManager().getPool().create();

get创建实体之前,还有很多工作要做,如果您必须了解实体池和“管理器”来创建它们,就感觉设计在泄漏实现细节。我认为诸如“池”和“经理”之类的东西(如果您坚持使用此名称)应该是ECS的实施细节,而不是暴露给客户的东西。

但不知道,我已经看到了一些非常缺乏想象力和一般用途ManagerHandlerAdapter,和这种名字,但我认为这是一个合理的可以原谅的使用情况下,当被称为“经理”的事情是“管理”(“控制实际负责? “,”处理?“)对象的生存期,负责创建和销毁对象并提供对它们的单独访问。至少对我来说,那是最直接,最直观的“经理”用法。

就是说,避免使用您的名字中的“经理”的一种通用策略就是去掉“经理”部分,并-s在最后添加一个。:-D例如,假设您有一个ThreadManager,它负责创建线程并提供有关它们的信息并访问它们等等,但是它不合并线程,因此我们不能调用它ThreadPool。好吧,在这种情况下,您只需调用它即可Threads。当我缺乏想象力时,这就是我要做的。

当“ Windows资源管理器”的类比等效物称为“文件管理器”时,我有点想起过去,就像这样:

在此处输入图片说明

我实际上认为在这种情况下,“文件管理器”比“ Windows资源管理器”更具描述性,即使有更好的名称不涉及“管理器”。因此,至少请不要太喜欢您的名字,而要开始命名“ ComponentExplorer”之类的东西。“ ComponentManager”至少让我对使用ECS引擎的人所做的事情有一个合理的了解。


同意 如果将一个类用于典型的管理职责(创建(“雇用”),销毁(“解雇”),监督和“雇员” /更高级别的界面),则该名称Manager是适当的。该类可能有设计问题,但名称通过点上。
贾斯汀时间2恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.