web-dev-qa-db-ja.com

コードで「マネージャー」を回避する方法

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

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

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

私のライブラリの新しいデザインでは、ライブラリを結合するために、これらの特定のクラスを持っています。

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

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

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

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

    • 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. ライブラリのデザインを変更する理由は、かなり変更できると思うからです。現時点では、その感触や流れが気に入らないからです。
27
miguel.martin

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

Dunkが言うように、Managerクラスは、物事を「管理」するため、このように呼び出されます。 「管理」は「データ」または「実行」のようなもので、ほとんどすべてを含むことができる抽象的なワードです。

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

これを修正するために変更したのは、コードで使用する語彙を変更することです。私は完全に一般的な単語を避けます(一般的なコードでない限り、一般的なライブラリを作成していない場合はまれです)。 I「マネージャー」や「オブジェクト」などの名前を付けることは避けます。

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

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

19
Klaim

さて、私はあなたがリンクしたコードのいくつかとあなたの投稿を読みましたが、私の正直な要約は、その大部分は基本的に完全に価値がないということです。ごめんなさい。つまり、あなたはこのコードをすべて持っていますが、何も達成していません。全然。ここで少し深く掘り下げる必要があるので、我慢してください。

ObjectFactory から始めましょう。まず、関数ポインタ。これらはステートレスであり、オブジェクトを作成するための唯一の有用なステートレスな方法はnewであり、そのためのファクトリは必要ありません。ステートフルにオブジェクトを作成することは有用であり、関数ポインターはこのタスクには役立ちません。そして、第二に、すべてのオブジェクトはitselfを破壊する方法を知る必要があり、それを破壊するためにその正確な作成インスタンスを見つける必要はありません。また、ユーザーの例外とリソースの安全のために、生のポインタではなく、スマートポインタを返す必要があります。 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();
    }
};
_

これは、処理しようとしているクラスから派生したクラスを処理しませんが、処理しませんでした。

これの残りはほとんど同じ話です。ここには、ライブラリを使用せずにはるかに優れた処理を行うことはできません。

このコードでマネージャーをどのように回避しますか?基本的にすべて削除します。

23
DeadMG

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

私の推測では、あなたのマネージャークラスはおそらく多くのことをします。それらを密接に関連する機能部分にセグメント化するとしたら、クラス名を見るだけで誰でも簡単に何ができるかがわかる機能部分に関連するクラス名を思い付くでしょう。これにより、設計の保守と理解がはるかに容易になります。

10
Dunk

私は実際、Managerがこの状況でそれほど悪いとは思わない、肩をすくめる。 Managerが許されると思う場所が1つある場合、それはエンティティ-コンポーネントシステムの場合です。これは、コンポーネントのライフタイムが「managing」であるためです。 、エンティティ、システム。少なくとも、ここでは、たとえば、Factoryより意味のある名前です。これは、ECSが実際に作成することよりも少し多くのことを行うため、このコンテキストではあまり意味がありません。

Databaseのように、ComponentDatabaseを使用できると思います。または、ComponentsSystemsEntitiesを使用することもできます(これは私が個人的に使用するものであり、主に単純に識別子が好きなためです。

私が少し不器用だと思う部分はこれです:

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

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

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

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

次のように、「Windowsエクスプローラー」の類義語が「ファイルマネージャー」と呼ばれたときのことを思い出します。

enter image description here

そして、実際には、この場合の「ファイルマネージャー」は、「マネージャー」に関係のないより良い名前があったとしても、「Windowsエクスプローラー」よりもわかりやすいと思います。ですから、少なくともあなたの名前をあまり気にしないで、「ComponentExplorer」のような名前を付け始めてください。 「ComponentManager」は、ECSエンジンを操作するために使用されていたものとして、少なくともそれが何をするのかを私に合理的に教えてくれます。

3
user204677