私のコードで「マネージャー」を避ける方法


26

現在、C ++向けにEntity Systemを再設計しています。多くのマネージャーがいます。私の設計では、ライブラリを結合するためにこれらのクラスがあります。「マネージャー」クラスに関しては、多くの悪いことを聞いたことがあります。おそらく、クラスに適切な名前を付けていません。しかし、他にどのような名前を付けるべきかはわかりません。

私のライブラリーのほとんどのマネージャーは、これらのクラスで構成されています(ただし、多少異なります)。

  • コンテナ-マネージャ内のオブジェクトのコンテナ
  • 属性-マネージャー内のオブジェクトの属性

ライブラリの新しいデザインには、ライブラリを結び付けるために、これらの特定のクラスがあります。

  • ComponentManager-エンティティシステムのコンポーネントを管理します

    • ComponentContainer
    • コンポーネント属性
    • シーン*-シーンへの参照(以下を参照)
  • SystemManager-エンティティシステム内のシステムを管理します

    • SystemContainer
    • シーン*-シーンへの参照(以下を参照)
  • EntityManager-エンティティシステム内のエンティティを管理します

    • EntityPool-エンティティのプール
    • EntityAttributes-エンティティの属性(これはComponentContainerおよびSystemクラスのみがアクセス可能です)
    • シーン*-シーンへの参照(以下を参照)
  • シーン-すべてのマネージャーを結び付ける

    • ComponentManager
    • SystemManager
    • EntityManager

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基本的に私はダンクが言及したことを聞いたことがあり、ライブラリにはたくさんの* Managerクラスがあります。
miguel.martin

これは、同様に役に立つことができますmedium.com/@wrong.about/...
Zapadlo

実用的なアプリケーションから有用なフレームワークを抽出します。想像されるアプリケーションに役立つフレームワークを書くことはほぼ不可能です。
ケビンクライン

回答:


19

DeadMGはコードの詳細にスポットを当てていますが、明確化に失敗しているように感じます。また、たとえば、ほとんどの高性能ビデオゲーム開発のような特定のコンテキストに当てはまらない彼の推奨事項のいくつかにも同意しません。しかし、彼はあなたのコードのほとんどが今のところ役に立たないことは世界的に正しいです。

Dunkが言うように、Managerクラスは物を「管理」するため、このように呼ばれます。「管理」は「データ」または「do」のようなもので、ほとんど何でも含むことができる抽象的な単語です。

私はほぼ7年前にあなたとほとんど同じ場所にいましたが、まだ何もしないようにコーディングするのが大変だったので、私の考え方に何か問題があると思い始めました。

これを修正するために変更したのは、コードで使用する語彙変更することです。私は完全に一般的な言葉を避けます(一般的なコードである場合を除き、一般的なライブラリを作成していない場合はまれです)。タイプに「Manager」または「object」という名前を付けることは避けます。

影響はコード内で直接発生します。つまり、タイプの実際の責任に対応する適切な単語を見つける必要があります。タイプがいくつかのことを行うように感じる場合(ブックのインデックスを保持し、それらを存続させ、ブックを作成/破棄する)、それぞれが1つの責任を持つ異なるタイプを必要とし、それらを使用するコードでそれらを組み合わせます。いつか工場が必要なときもあれば、そうでないときもあります。いつかレジストリが必要になるので、レジストリをセットアップします(標準のコンテナとスマートポインタを使用)。時には、いくつかの異なるサブシステムで構成されるシステムが必要になるため、できる限りすべてを分離して、各パーツが何か役に立つようにします。

タイプに「マネージャー」という名前を付けないでください。また、すべてのタイプに単一の固有の役割あることを確認してください。いつか名前を見つけるのは難しいかもしれませんが、プログラミング全般で行うのが最も難しいことの1つです


カップルdynamic_castがパフォーマンスを低下させている場合は、静的型システムを使用します-それが目的です。LLVMのように独自のRTTIを展開する必要があるのは、実際には一般的なコンテキストではなく、特殊なコンテキストです。それでも、彼らはこれをしません。
DeadMG

@DeadMG私はこの部分については特に話していませんでしたが、ほとんどの高性能ゲームは、たとえば、一般的にプログラミングに適した新しいものや他のものでさえ動的に割り当てることはできません。それらは、一般化できない特定のハードウェアのための特定の獣であり、これが「依存する」がより一般的な答えである理由です。特にC ++の場合です。(ゲーム開発者の誰もが動的キャストを使用していません。プラグインを入手するまでは役に立たないため、それでもまれです)。
クライム

@DeadMG dynamic_castを使用しておらず、dynamic_castの代替を使用していません。私はそれをライブラリーに実装しましたが、それを取り出すのは面倒でした。template <typename T> class Class { ... };実際には、異なるクラス(つまり、カスタムコンポーネントとカスタムシステム)にIDを割り当てるためのものです。IDの割り当ては、マップがゲームに必要なパフォーマンスをもたらさない可能性があるため、コンポーネント/システムをベクターに格納するために使用されます。
miguel.martin

まあ、実際にはdynamic_castの代替も実装せず、偽のtype_idだけを実装しました。しかし、それでもtype_idが達成したことは望ましくありませんでした。
miguel.martin

「あなたのタイプの本当の責任に対応する正しい言葉を見つけてください」-私はこれに完全に同意しますが、多くの場合、開発者コミュニティが特定のタイプの責任について合意した単一の単語はありません。
bytedev 14

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*)>>()>>が、既存の機能を使用する代わりに、そのクラスを作成しました。それは同じですが、完全に不必要にグローバルな可変状態であり、たくさんのくだらないマクロがあります。

ここで私が言っているのは、全体を標準クラスから構築された機能で置き換えることができるクラスを作成し、それらをグローバルな可変状態にしてマクロの束を含めることでさらに悪化させたことですおそらくあなたができる最悪のこと。

次に、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));
    }

問題は解決しましたが、前の200行のマクロやその他の問題はありませんでした。また、これによりIdentifiableArrayは何もマークされませんがstd::vector、一意の所有権または非所有権の方が優れている場合でも、参照カウントを使用する必要があります。

ああ、Typesには絶対に役に立たない 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
学ぶのに少し時間がかかりましたが、最も信頼性が高く、バグのないコードは、そこにないコードです。
タクロイ

1
std :: type_infoを使用しなかった理由は、RTTI を避けようとしたためです。私は、フレームワークは基本的に私が使用していない理由である、ゲームを狙った言及しましたtypeiddynamic_cast。しかし、フィードバックをありがとう、私はそれを感謝しています。
miguel.martin

この批判は、コンポーネントエンティティシステムのゲームエンジンに関する知識に基づいていますか、それとも一般的なC ++コードレビューですか?
デン

1
@Den何よりもデザインの問題だと思います。
miguel.martin

10

Managerクラスの問題は、マネージャーが何でもできることです。クラス名を読んで、クラスが何をするかを知ることはできません。EntityManager ....エンティティで何かを行うことは知っていますが、誰が何を知っているのでしょうか。SystemManager ...はい、システムを管理します。それはとても役に立ちます。

私の推測では、あなたのマネージャークラスはおそらく多くのことをしているでしょう。それらを密接に関連する機能部分に分割すると、おそらく、機能部分に関連するクラス名を思い付くことができるでしょう。だれもがクラス名を見るだけで、彼らが何をするかを伝えることができます。これにより、設計の維持と理解がはるかに容易になります。


1
+1だけでなく、彼らは何でもできるようになるだけでなく、十分に長いタイムラインで彼らはそうするでしょう。「マネージャー」記述子は、kitchen-sink / swiss-army-knifeクラスを作成するための招待状と見なされます。
エリックディートリッヒ

@Erik-ああ、そうです、良いクラス名を持つことは、クラスが何ができるかを誰かに伝えるので、重要なだけではないことを付け加えるべきでした。ただし、同様にクラス名はクラスがすべきでないことも示しています。
ダンク

3

私は実際Manager、この文脈ではそれほど悪いとは思いません、肩をすくめます。Manager許されると思う場所が1つある場合、それは実際にコンポーネント、エンティティ、およびシステムのライフタイムを「管理」しているため、エンティティコンポーネントシステムになります。少なくとも、ここでは言うよりも意味のある名前です。Factoryこれは、ECSが本当に物を作成する以上のことを行うため、このコンテキストではあまり意味がありません。

Databaseように使用できると思いますComponentDatabase。またはComponents、単に、、Systemsおよびを使用する場合がありますEntities(これは私が個人的に使用するものであり、主に私は簡潔な識別子が好きだからです。

私が少し不器用だと感じる部分はこれです:

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

getエンティティを作成する前に、これは非常に多くのことであり、エンティティプールとそれらを作成するための「マネージャ」について知る必要がある場合、設計が実装の詳細を少し漏らしているように感じます。「プール」や「マネージャー」のようなもの(この名前に固執する場合)は、クライアントに公開されるものではなく、ECSの実装の詳細であるべきだと思います。

しかし、知らん、私はいくつかの非常に想像力に欠けるとの一般的な使い方を見てきましたManagerHandlerAdapter、およびこの種の名前が、「マネージャー」というものは、実際に「制御(「管理」を担当していたとき、私はこれが合理的に許さユースケースだと思いますか? 「、「処理しますか?」)オブジェクトの存続期間。個々にオブジェクトへのアクセスを作成および破棄し、アクセスを提供します。少なくとも私にとっては、これが最も簡単で直感的な「マネージャー」の使用です。

ただし、名前に「マネージャー」を避けるための一般的な戦略の1つは、「マネージャー」部分を取り出し-sて最後に追加することです。:-Dたとえば、あなたが持っていて、ThreadManagerスレッドを作成し、それらに関する情報を提供し、それらにアクセスするなどの責任があるとしますが、スレッドをプールしないので、それを呼び出すことはできませんThreadPool。まあ、その場合は、単にそれを呼び出しますThreads。とにかく私が想像力に欠けるとき、それは私がすることです。

「Windowsエクスプローラー」に相当するものが「ファイルマネージャー」と呼ばれていたときのことを思い出します。

ここに画像の説明を入力してください

そして、実際には、この場合の「ファイルマネージャ」は、「マネージャ」を含まないより良い名前がそこにあっても、「Windowsエクスプローラ」よりも記述的だと思います。だから、少なくともあなたの名前に夢中にならず、「ComponentExplorer」のような名前を付け始めてください。「ComponentManager」は少なくとも、誰かがECSエンジンでの作業に慣れていたときに何ができるかについての合理的な考えを与えてくれます。


同意した。クラスが一般的な管理職務(作成(「雇用」)、破壊(「発砲」)、監視、および「従業員」/上位組織のインターフェースに使用される場合、名前Managerは適切です。クラスには設計上の問題がありますが、スポットオンです。
ジャスティン時間2復職モニカ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.