Bagaimana cara menghindari "manajer" dalam kode saya

26

Saat ini saya sedang merancang ulang Sistem Entitas saya , untuk C ++, dan saya memiliki banyak Manajer. Dalam desain saya, saya memiliki kelas-kelas ini, untuk mengikat perpustakaan saya bersama. Saya telah mendengar banyak hal buruk ketika datang ke kelas "manajer", mungkin saya tidak menyebutkan kelas saya dengan tepat. Namun, saya tidak tahu apa lagi untuk memberi nama mereka.

Sebagian besar manajer, di perpustakaan saya, terdiri dari kelas-kelas ini (meskipun sedikit berbeda):

  • Wadah - wadah untuk objek di manajer
  • Atribut - atribut untuk objek di manajer

Dalam desain baru saya untuk perpustakaan saya, saya memiliki kelas-kelas khusus ini, untuk mengikat perpustakaan saya bersama-sama.

  • ComponentManager - mengelola komponen dalam Sistem Entitas

    • ComponentContainer
    • Komponen Komponen
    • Adegan * - referensi ke Adegan (lihat di bawah)
  • SystemManager - mengelola sistem dalam Sistem Entitas

    • SystemContainer
    • Adegan * - referensi ke Adegan (lihat di bawah)
  • EntityManager - mengelola entitas dalam Sistem Entity

    • EntityPool - kumpulan entitas
    • EntityAttributes - atribut entitas (ini hanya akan dapat diakses oleh kelas ComponentContainer dan Sistem)
    • Adegan * - referensi ke Adegan (lihat di bawah)
  • Adegan - mengikat semua manajer bersama

    • Manajer Komponen
    • Manajer sistem
    • Manajer Entity

Saya berpikir untuk hanya menempatkan semua wadah / kolam di kelas Scene itu sendiri.

yaitu

Alih-alih ini:

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();

Ini akan menjadi ini:

Scene scene; // create a Scene

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

Tapi saya tidak yakin. Jika saya melakukan yang terakhir, itu berarti saya akan memiliki banyak objek dan metode yang dideklarasikan di dalam kelas Scene saya.

CATATAN:

  1. Sistem entitas hanyalah desain yang digunakan untuk game. Ini terdiri dari 3 bagian utama: komponen, entitas, dan sistem. Komponen hanyalah data, yang dapat "ditambahkan" ke entitas, agar entitas menjadi berbeda. Entitas diwakili oleh integer. Sistem mengandung logika untuk suatu entitas, dengan komponen tertentu.
  2. Alasan saya mengubah desain untuk perpustakaan saya, adalah karena saya pikir itu bisa diubah cukup banyak, saya tidak suka rasa / aliran ke sana, saat ini.
miguel.martin
sumber
Bisakah Anda menguraikan hal-hal buruk yang pernah Anda dengar tentang manajer dan bagaimana mereka berhubungan dengan Anda?
MrFox
@ McFox Pada dasarnya saya telah mendengar apa yang disebutkan Dunk, dan saya memiliki banyak kelas * Manajer di perpustakaan saya, yang ingin saya singkirkan.
miguel.martin
Ini juga bisa membantu: medium.com/@wrong.about/…
Zapadlo
Anda memfaktorkan kerangka kerja yang bermanfaat dari aplikasi yang berfungsi. Hampir tidak mungkin untuk menulis kerangka kerja yang berguna untuk aplikasi yang dibayangkan.
kevin cline

Jawaban:

19

DeadMG tepat pada spesifik kode Anda tetapi saya merasa seperti itu kehilangan klarifikasi. Juga saya tidak setuju dengan beberapa rekomendasinya yang tidak berlaku dalam konteks tertentu seperti kebanyakan pengembangan videogame berkinerja tinggi. Tapi dia secara global benar bahwa sebagian besar kode Anda tidak berguna saat ini.

Seperti yang dikatakan Dunk, kelas Manajer dipanggil seperti ini karena mereka "mengelola" berbagai hal. "kelola" seperti "data" atau "lakukan", ini adalah kata abstrak yang dapat berisi hampir semua hal.

Saya sebagian besar berada di tempat yang sama dengan Anda sekitar 7 tahun yang lalu, dan saya mulai berpikir bahwa ada sesuatu yang salah dalam cara berpikir saya karena begitu banyak upaya untuk membuat kode untuk tidak melakukan apa pun.

Apa yang saya ubah untuk memperbaikinya adalah mengubah kosakata yang saya gunakan dalam kode. Saya benar-benar menghindari kata-kata umum (kecuali kode generik, tetapi jarang ketika Anda tidak membuat perpustakaan umum). Saya menghindari menyebutkan nama "Manajer" atau "objek" apa pun.

Dampaknya langsung dalam kode: itu memaksa Anda untuk menemukan kata yang tepat sesuai dengan tanggung jawab nyata dari tipe Anda. Jika Anda merasa jenis melakukan beberapa hal (membuat indeks Buku, tetap hidup, membuat / menghancurkan buku) maka Anda memerlukan berbagai jenis yang masing-masing akan memiliki satu tanggung jawab, kemudian Anda menggabungkannya dalam kode yang menggunakannya. Kadang saya butuh pabrik, kadang tidak. Kadang saya membutuhkan registry, jadi saya setup satu (menggunakan kontainer standar dan smart pointer). Kadang-kadang saya memerlukan sistem yang terdiri dari beberapa sub sistem yang berbeda sehingga saya memisahkan semuanya sebanyak yang saya bisa sehingga setiap bagian melakukan sesuatu yang bermanfaat.

Jangan pernah menyebutkan tipe "manajer" dan pastikan semua tipe Anda memiliki peran unik adalah rekomendasi saya. Mungkin sulit untuk menemukan nama kapan-kapan, tetapi itu adalah salah satu hal tersulit untuk dilakukan dalam pemrograman secara umum

Klaim
sumber
Jika pasangan dynamic_castmembunuh kinerja Anda, maka gunakan sistem tipe statis - itulah gunanya. Ini benar-benar konteks khusus, bukan yang umum, harus memutar RTTI Anda sendiri seperti yang dilakukan LLVM. Bahkan kemudian, mereka tidak melakukan ini.
DeadMG
@DeadMG Saya tidak membicarakan bagian ini secara khusus, tetapi sebagian besar game berkinerja tinggi tidak mampu, misalnya, alokasi dinamis melalui hal-hal baru atau bahkan lainnya yang baik untuk pemrograman pada umumnya. Mereka adalah binatang buas khusus untuk perangkat keras tertentu yang Anda tidak dapat menggeneralisasi yang mengapa "itu tergantung" adalah jawaban yang lebih umum, khususnya dengan C ++. (Omong-omong, tidak ada seorang pun di game dev yang menggunakan pemeran dinamis, itu tidak pernah berguna sampai Anda memiliki plugin, dan bahkan kemudian jarang)
Klaim
@DeadMG Saya tidak menggunakan dynamic_cast, saya juga tidak menggunakan alternatif untuk dynamic_cast. Saya menerapkannya di perpustakaan, tetapi saya tidak bisa repot-repot mengeluarkannya. template <typename T> class Class { ... };sebenarnya untuk menetapkan ID untuk kelas yang berbeda (yaitu komponen khusus dan sistem khusus). Penugasan ID digunakan untuk menyimpan komponen / sistem dalam vektor, karena peta mungkin tidak membawa kinerja yang saya butuhkan untuk sebuah game.
miguel.martin
Yah saya sebenarnya tidak menerapkan alternatif untuk dynamic_cast juga, hanya tip_id palsu. Tapi, tetap saja saya tidak ingin apa yang dicapai type_id.
miguel.martin
"temukan kata yang tepat sesuai dengan tanggung jawab nyata tipe Anda" - meskipun saya sepenuhnya setuju dengan ini, seringkali tidak ada satu kata pun (jika ada) yang disetujui oleh komunitas pengembang untuk jenis tanggung jawab tertentu.
bytedev
23

Yah, saya telah membaca beberapa kode yang Anda tautkan, dan posting Anda, dan ringkasan jujur ​​saya adalah bahwa sebagian besar pada dasarnya sama sekali tidak berharga. Maaf. Maksud saya, Anda memiliki semua kode ini, tetapi Anda belum mencapai apa pun. Sama sekali. Aku harus pergi ke kedalaman di sini, jadi bersabarlah denganku.

Mari kita mulai dengan ObjectFactory . Pertama, pointer fungsi. Ini adalah stateless, satu-satunya cara stateless yang berguna untuk membuat objek adalah newdan Anda tidak memerlukan pabrik untuk itu. Membuat objek secara stateful adalah hal yang berguna dan fungsi pointer tidak berguna untuk tugas ini. Dan kedua, setiap objek perlu tahu cara menghancurkan dirinya sendiri , tidak harus menemukan contoh yang tepat untuk menghancurkannya. Dan di samping itu, Anda harus mengembalikan pointer cerdas, bukan pointer mentah, untuk pengecualian dan keamanan sumber daya pengguna Anda. Seluruh kelas ClassRegistryData Anda adil std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>, tetapi dengan lubang yang disebutkan di atas. Tidak perlu ada sama sekali.

Kemudian kita memiliki AllocatorFunctor - sudah ada antarmuka pengalokasi standar yang disediakan - Belum lagi mereka hanya pembungkus delete obj;dan return new T;- yang lagi-lagi sudah disediakan, dan mereka tidak akan berinteraksi dengan pengalokasi memori stateful atau kustom yang ada.

Dan ClassRegistry sederhana std::map<std::string, std::function<std::unique_ptr<Base, std::function<void(Base*)>>()>>tetapi Anda menulis kelas untuk itu alih-alih menggunakan fungsi yang ada. Itu sama, tetapi benar-benar tidak bisa diubah negara global dengan sekelompok makro jelek.

Apa yang saya katakan di sini adalah bahwa Anda telah membuat beberapa kelas di mana Anda dapat mengganti semuanya dengan fungsionalitas yang dibangun dari kelas Standar, dan kemudian membuatnya lebih buruk dengan menjadikannya negara yang bisa berubah secara global dan melibatkan banyak makro, yang merupakan hal terburuk yang bisa Anda lakukan.

Kemudian kami memiliki Identifikasi . Ini banyak cerita yang sama di sini - std::type_infosudah melakukan tugas mengidentifikasi setiap jenis sebagai nilai run-time, dan itu tidak memerlukan boilerplate yang berlebihan pada bagian dari pengguna.

    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));
    }

Masalah terpecahkan, tetapi tanpa dua ratus baris makro sebelumnya dan yang lainnya. Ini juga menandai IdentifiableArray Anda sebagai apa pun kecuali std::vectorAnda harus menggunakan penghitungan referensi meskipun kepemilikan unik atau non-kepemilikan akan lebih baik.

Oh, dan apakah saya menyebutkan bahwa Jenis berisi sekelompok typedef yang sama sekali tidak berguna ? Anda benar-benar tidak mendapatkan keuntungan apa pun jika menggunakan jenis mentah secara langsung, dan kehilangan sedikit keterbacaan.

Selanjutnya adalah ComponentContainer . Anda tidak dapat memilih kepemilikan, Anda tidak dapat memiliki wadah terpisah untuk kelas turunan dari berbagai komponen, Anda tidak dapat menggunakan semantik seumur hidup Anda sendiri. Hapus tanpa penyesalan.

Sekarang, ComponentFilter mungkin benar-benar bernilai sedikit, jika Anda banyak refactored. Sesuatu seperti

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();
    }
};

Ini tidak berurusan dengan kelas yang berasal dari kelas yang Anda coba atasi, tetapi kelas Anda juga tidak.

Selebihnya dari kisah ini hampir sama. Tidak ada apa pun di sini yang tidak dapat dilakukan dengan lebih baik tanpa menggunakan perpustakaan Anda.

Bagaimana Anda menghindari manajer dalam kode ini? Hapus pada dasarnya semua itu.

DeadMG
sumber
14
Butuh waktu beberapa saat untuk saya pelajari, tetapi potongan kode yang paling dapat diandalkan dan bebas bug adalah yang tidak ada di sana .
Tacroy
1
Alasan saya tidak menggunakan std :: type_info adalah karena saya mencoba menghindari RTTI. Saya menyebutkan kerangka itu ditujukan untuk permainan, yang pada dasarnya alasan saya tidak menggunakan typeidatau dynamic_cast. Terima kasih atas umpan baliknya, saya menghargainya.
miguel.martin
Apakah kritik ini didasarkan pada pengetahuan Anda tentang mesin-mesin sistem komponen-entitas atau itu review kode C ++ umum?
Den
1
@Den Saya lebih memikirkan masalah desain daripada yang lain.
miguel.martin
10

Masalah dengan kelas-kelas Manajer adalah bahwa seorang manajer dapat melakukan apa saja. Anda tidak dapat membaca nama kelas dan tahu apa yang dikerjakan kelas. EntityManager .... Saya tahu itu melakukan sesuatu dengan Entitas tetapi siapa yang tahu apa. SystemManager ... ya, itu mengelola sistem. Itu sangat membantu.

Dugaan saya adalah bahwa kelas manajer Anda mungkin melakukan banyak hal. Saya berani bertaruh jika Anda membagi hal-hal itu menjadi bagian-bagian fungsional yang terkait erat, maka Anda mungkin akan dapat menghasilkan nama-nama kelas yang terkait dengan bagian-bagian fungsional yang setiap orang dapat mengatakan apa yang mereka lakukan hanya dengan melihat nama kelas. Ini akan membuat pemeliharaan dan pemahaman desain menjadi jauh lebih mudah.

Dunk
sumber
1
+1 dan tidak hanya mereka dapat melakukan apa pun, pada jangka waktu yang cukup lama. Orang-orang melihat deskriptor "Manajer" sebagai undangan untuk membuat kelas pisau dapur / cuci-pisau tentara.
Erik Dietrich
@ Erik - Ah ya, saya seharusnya menambahkan bahwa memiliki nama kelas yang baik tidak hanya penting karena memberi tahu seseorang apa yang dapat dilakukan oleh kelas; tetapi sama-sama nama kelasnya juga mengatakan apa yang tidak boleh dilakukan kelas.
Dunk
3

Saya sebenarnya tidak berpikir Managersangat buruk dalam konteks ini, mengangkat bahu. Jika ada satu tempat di mana saya pikir Managerdimaafkan, itu akan untuk sistem entitas-komponen, karena itu benar-benar " mengelola " masa hidup komponen, entitas, dan sistem. Paling tidak itu adalah nama yang lebih bermakna di sini daripada, katakanlah, Factoryyang tidak masuk akal dalam konteks ini karena ECS benar-benar melakukan sedikit lebih banyak daripada menciptakan sesuatu.

Saya kira Anda bisa menggunakan Database, seperti ComponentDatabase. Atau Anda mungkin hanya menggunakan Components,, Systemsdan Entities(ini adalah apa yang saya gunakan secara pribadi dan terutama hanya karena saya suka pengidentifikasi singkat meskipun diakui menyampaikan informasi lebih sedikit, mungkin, daripada "Manajer").

Satu bagian yang saya temukan agak kikuk adalah ini:

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

Itu banyak hal getsebelum Anda dapat membuat entitas dan rasanya desain bocor sedikit detail implementasi jika Anda harus tahu tentang kumpulan entitas dan "manajer" untuk membuatnya. Saya pikir hal-hal seperti "kumpulan" dan "manajer" (jika Anda berpegang pada nama ini) harus menjadi detail implementasi ECS, bukan sesuatu yang terpapar pada klien.

Tapi tak tahu, saya telah melihat beberapa penggunaan yang sangat imajinatif dan generik Manager, Handler, Adapter, dan nama-nama semacam ini, tapi saya pikir ini adalah kasus penggunaan cukup dimaafkan ketika hal yang disebut "Manager" sebenarnya bertanggung jawab untuk "mengelola" ( "mengendalikan? "," handling? ") masa hidup objek, yang bertanggung jawab untuk membuat dan menghancurkan dan menyediakan akses kepada mereka secara individual. Setidaknya itu adalah penggunaan "Manajer" yang paling mudah dan intuitif bagi saya.

Yang mengatakan, satu strategi umum untuk menghindari "Manajer" di nama Anda hanya mengambil bagian "Manajer" dan menambahkan -sdi akhir. :-D Sebagai contoh, katakanlah Anda memiliki ThreadManagerdan bertanggung jawab untuk membuat utas dan memberikan informasi tentang mereka dan mengaksesnya dan sebagainya, tetapi itu tidak mengumpulkan utas jadi kami tidak dapat menyebutnya ThreadPool. Nah, kalau begitu, sebut saja begitu Threads. Lagi pula itulah yang saya lakukan ketika saya tidak imajinatif.

Saya agak diingatkan kembali ketika padanan analog dari "Windows Explorer" disebut "File Manager", seperti:

masukkan deskripsi gambar di sini

Dan saya benar-benar berpikir "File Manager" dalam hal ini lebih deskriptif daripada "Windows Explorer" bahkan jika ada beberapa nama yang lebih baik di luar sana yang tidak melibatkan "Manager". Jadi setidaknya tolong jangan terlalu suka dengan nama Anda dan mulai memberi nama hal-hal seperti "ComponentExplorer". "ComponentManager" setidaknya memberi saya ide yang masuk akal tentang apa yang dilakukan orang itu yang bekerja dengan mesin ECS.


sumber
Sepakat. Jika suatu kelas digunakan untuk tugas-tugas manajerial yang khas (menciptakan ("mempekerjakan"), menghancurkan ("menembak"), mengawasi, dan "karyawan" / antarmuka yang lebih tinggi, maka namanya Managersesuai. Kelas mungkin memiliki masalah desain , tetapi nama tepat waktu
Justin Time 2 Reinstate Monica