मेरे कोड में "प्रबंधकों" से कैसे बचें


26

मैं वर्तमान में C ++ के लिए अपने एंटिटी सिस्टम को फिर से डिजाइन कर रहा हूं , और मेरे पास बहुत सारे प्रबंधक हैं। मेरे डिज़ाइन में, मेरी लाइब्रेरी को एक साथ बाँधने के लिए, मेरे पास ये कक्षाएं हैं। जब मैंने "प्रबंधक" कक्षाओं की बात आती है, तो मैंने बहुत सारी बुरी बातें सुनी हैं, शायद मैं अपनी कक्षाओं को उचित रूप से नाम नहीं दे रहा हूं। हालांकि, मुझे नहीं पता कि उनका नाम क्या है।

मेरे पुस्तकालय में अधिकांश प्रबंधक, इन वर्गों से बने होते हैं (हालाँकि यह थोड़ा भिन्न होता है):

  • कंटेनर - प्रबंधक में वस्तुओं के लिए एक कंटेनर
  • विशेषताएँ - प्रबंधक में वस्तुओं के लिए विशेषताएँ

मेरी लाइब्रेरी के लिए मेरे नए डिजाइन में, मेरी लाइब्रेरी को एक साथ टाई करने के लिए, मेरे पास ये विशिष्ट कक्षाएं हैं।

  • ComponentManager - इकाई प्रणाली में घटकों का प्रबंधन करता है

    • ComponentContainer
    • ComponentAttributes
    • दृश्य * - दृश्य का संदर्भ (नीचे देखें)
  • SystemManager - इकाई प्रणाली में सिस्टम का प्रबंधन करता है

    • SystemContainer
    • दृश्य * - दृश्य का संदर्भ (नीचे देखें)
  • EntityManager - इकाई प्रणाली में संस्थाओं का प्रबंधन करता है

    • EntityPool - संस्थाओं का एक पूल
    • EntityAttributes - एक इकाई का गुण (यह केवल CompContainer और System classes के लिए सुलभ होगा)
    • दृश्य * - दृश्य का संदर्भ (नीचे देखें)
  • दृश्य - सभी प्रबंधकों को एक साथ जोड़ता है

    • ComponentManager
    • व्यवस्था प्रबंधक
    • EntityManager

मैं सिर्फ दृश्य में ही सभी कंटेनर / पूल को रखने की सोच रहा था।

अर्थात

इसके अलावा:

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

लेकिन, मैं अनिश्चित हूं। यदि मैं बाद में करने वाला था, तो इसका मतलब होगा कि मेरे दृश्य वर्ग के अंदर बहुत सी वस्तुएं और विधियां होंगी।

टिप्पणियाँ:

  1. एक इकाई प्रणाली बस एक डिजाइन है जो खेलों के लिए उपयोग की जाती है। यह 3 प्रमुख भागों से बना है: घटक, निकाय और सिस्टम। घटक बस डेटा हैं, जो संस्थाओं के लिए "जोड़ा" जा सकता है, ताकि संस्थाओं को विशिष्ट बनाया जा सके। एक इकाई को एक पूर्णांक द्वारा दर्शाया जाता है। सिस्टम में विशिष्ट घटकों के साथ, एक इकाई के लिए तर्क होते हैं।
  2. मैं अपने पुस्तकालय के लिए अपने डिजाइन को बदल रहा हूं, इसका कारण यह है कि मुझे लगता है कि इसे काफी बदला जा सकता है, फिलहाल मुझे यह पसंद नहीं है।

क्या आप उन बुरी चीज़ों के बारे में विस्तार से बता सकते हैं जो आपने प्रबंधकों के बारे में सुनी हैं और वे आपसे कैसे संबंधित हैं?
19


@MrFox मूल रूप से मैंने सुना है कि डंक ने क्या उल्लेख किया है, और मेरी लाइब्रेरी में मेरे पास बहुत सारे * प्रबंधक वर्ग हैं, जिनसे मैं छुटकारा पाना चाहता हूं।
मिग्वेलमार्टिन

यह रूप में अच्छी तरह से उपयोगी हो सकता है: medium.com/@wrong.about/...
Zapadlo

आप एक काम कर रहे आवेदन से बाहर एक उपयोगी रूपरेखा कारक। कल्पित अनुप्रयोगों के लिए एक उपयोगी रूपरेखा लिखना लगभग असंभव है।
केविन क्लाइन

जवाबों:


19

डेडएमजी आपके कोड की बारीकियों पर हाजिर है लेकिन मुझे ऐसा लगता है जैसे यह एक स्पष्टीकरण याद करता है। इसके अलावा, मैं उनकी कुछ अनुशंसाओं से सहमत नहीं हूं जो उदाहरण के लिए सबसे उच्च प्रदर्शन-वीडियोगेम विकास जैसे कुछ विशिष्ट संदर्भों में नहीं रखती हैं। लेकिन वह विश्व स्तर पर सही है कि आपके अधिकांश कोड अभी उपयोगी नहीं हैं।

जैसा कि डंक कहते हैं, प्रबंधक वर्गों को इस तरह कहा जाता है क्योंकि वे चीजों का "प्रबंधन" करते हैं। "मैनेज" "डेटा" या "डू" जैसा है, यह एक अमूर्त शब्द है जिसमें लगभग कुछ भी हो सकता है।

मैं लगभग 7 साल पहले आप की तुलना में एक ही जगह पर था, और मैंने यह सोचना शुरू कर दिया कि मेरे सोचने के तरीके में कुछ गड़बड़ है क्योंकि अभी तक कुछ भी नहीं करने के लिए कोड करने के लिए बहुत प्रयास किए गए थे।

मैंने इसे ठीक करने के लिए जो कोड में उपयोग की गई शब्दावली को बदलना है । मैं पूरी तरह से जेनेरिक शब्दों से बचता हूं (जब तक कि यह सामान्य कोड नहीं है, लेकिन यह दुर्लभ है जब आप जेनेरिक लाइब्रेरी नहीं बना रहे हैं)। मैं किसी भी प्रकार के "प्रबंधक" या "ऑब्जेक्ट" का नाम लेने से बचता हूं ।

प्रभाव कोड में प्रत्यक्ष है: यह आपको अपने प्रकार की वास्तविक जिम्मेदारी के अनुरूप सही शब्द खोजने के लिए मजबूर करता है। यदि आपको लगता है कि प्रकार कई चीजें करता है (पुस्तकों का एक सूचकांक रखें, उन्हें जीवित रखें, पुस्तकें बनाएं / नष्ट करें) तो आपको अलग-अलग प्रकार की आवश्यकता होती है कि प्रत्येक की एक जिम्मेदारी होगी, फिर आप उन्हें उस कोड में जोड़ते हैं जो उनका उपयोग करता है। कभी-कभी मुझे एक कारखाने की आवश्यकता होती है, कभी-कभी नहीं। कभी-कभी मुझे एक रजिस्ट्री की आवश्यकता होती है, इसलिए मैं एक (मानक कंटेनरों और स्मार्ट पॉइंटर्स का उपयोग करके) सेटअप करता हूं। कभी-कभी मुझे एक ऐसी प्रणाली की आवश्यकता होती है, जो कई अलग-अलग उप-प्रणालियों से बनी होती है, इसलिए मैं हर चीज को उतना अलग करता हूं जितना कि मैं कर सकता हूं ताकि प्रत्येक भाग कुछ उपयोगी हो।

कभी भी एक प्रकार का "प्रबंधक" नाम न दें और सुनिश्चित करें कि आपके सभी प्रकार की एकल भूमिका मेरी सिफारिश है। हो सकता है कि कुछ समय के लिए नामों को खोजना कठिन हो, लेकिन आमतौर पर प्रोग्रामिंग में यह सबसे कठिन काम है


यदि कोई युगल dynamic_castआपके प्रदर्शन को मार रहा है, तो स्थैतिक प्रकार प्रणाली का उपयोग करें- यही वह है। यह वास्तव में एक विशेष संदर्भ है, एक सामान्य नहीं, एलएलवीएम की तरह अपने स्वयं के आरटीटीआई को रोल करने के लिए। फिर भी, वे ऐसा नहीं करते हैं।
डेडएमजी

@DeadMG मैं विशेष रूप से इस भाग के बारे में बात नहीं कर रहा था, लेकिन अधिकांश उच्च-प्रदर्शन वाले खेल बर्दाश्त नहीं कर सकते हैं, उदाहरण के लिए, नए या अन्य सामानों के माध्यम से गतिशील आवंटन जो सामान्य रूप से प्रोग्रामिंग के लिए ठीक हैं। वे विशिष्ट हार्डवेयर के लिए विशिष्ट जानवर हैं जो आप सामान्य नहीं कर सकते हैं यही कारण है कि "यह निर्भर करता है" एक अधिक सामान्य जवाब है, विशेष रूप से सी ++ के साथ। (गेम देव में कोई भी गतिशील कलाकारों का उपयोग नहीं करता है, यह तब तक उपयोगी नहीं है जब तक आपके पास प्लगइन्स नहीं हैं, और तब भी यह दुर्लभ है)।
Klaim

@DeadMG मैं डायनामिक_कास्ट का उपयोग नहीं कर रहा हूं, न ही मैं डायनामिक_कास्ट के विकल्प का उपयोग कर रहा हूं। मैंने इसे लाइब्रेरी में लागू किया, लेकिन मैं इसे बाहर निकालने से परेशान नहीं हो सकता। template <typename T> class Class { ... };वास्तव में विभिन्न वर्गों (अर्थात् कस्टम घटकों और कस्टम सिस्टम) के लिए आईडी असाइन करने के लिए है। आईडी असाइनमेंट का उपयोग वेक्टर में घटकों / प्रणालियों को संग्रहीत करने के लिए किया जाता है, क्योंकि नक्शे में मुझे किसी खेल के लिए आवश्यक प्रदर्शन नहीं मिल सकता है।
मिग्वेलमार्टिन

वास्तव में मैंने वास्तव में डायनामिक_कास्ट के विकल्प को लागू नहीं किया है, बस एक नकली टाइप_आईडी। लेकिन, फिर भी मैं नहीं चाहता था कि type_id ने क्या हासिल किया।
मिग्वेलमार्टिन

"अपने प्रकार की वास्तविक जिम्मेदारी के अनुरूप सही शब्द ढूंढें" - हालांकि मैं इस बात से पूरी तरह सहमत हूं, अक्सर एक भी शब्द (यदि कोई हो) नहीं है जो डेवलपर समुदाय ने एक विशेष प्रकार की जिम्मेदारी के लिए सहमति व्यक्त की है।
बट्टेव

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*)>>()>>लेकिन आपने मौजूदा कार्यक्षमता का उपयोग करने के बजाय इसके लिए एक वर्ग लिखा है। यह एक ही है, लेकिन भद्दे मैक्रों के झुंड के साथ पूरी तरह से अनावश्यक रूप से वैश्विक पारस्परिक अवस्था है।

जो मैं यहां कह रहा हूं वह यह है कि आपने कुछ कक्षाएं बनाई हैं जहां आप मानक कक्षाओं से निर्मित कार्यक्षमता के साथ पूरी चीज को बदल सकते हैं, और फिर उन्हें वैश्विक उत्परिवर्ती राज्य बनाकर और मैक्रों के एक समूह को शामिल करके और भी बदतर बना दिया है, जो कि है सबसे बुरा काम आप संभवतः कर सकते हैं।

फिर हमारे पास पहचान योग्य है । यह यहाँ एक ही कहानी का एक बहुत है- 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));
    }

समस्या हल हो गई, लेकिन मैक्रों और व्हॉटन की पूर्ववर्ती दो सौ पंक्तियों के बिना। यह आपके आइडेंटिफायरेबल ऐरे को भी चिन्हित करता है, std::vectorलेकिन आपको यूनिक-ओनरशिप या नॉन-ओनरशिप बेहतर होगा, भले ही आपको रेफरेंस-काउंटिंग का उपयोग करना पड़े।

ओह, और क्या मैंने उल्लेख किया है कि प्रकारों में बिल्कुल बेकार टाईपडेफ़्स का एक गुच्छा होता है ? आप वास्तव में कोई फायदा नहीं जो सीधे कच्चे प्रकार का उपयोग करने पर, और पठनीयता का एक अच्छा हिस्सा खो देते हैं।

अगला अप कंपोनेंटकॉनटेनर है । आप स्वामित्व का चयन नहीं कर सकते हैं, आपके पास विभिन्न घटकों के व्युत्पन्न वर्गों के लिए अलग कंटेनर नहीं हो सकते हैं, आप अपने स्वयं के जीवन भर के शब्दार्थों का उपयोग नहीं कर सकते। पश्चात्ताप के बिना हटाएं।

अब, 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 से बचने की कोशिश कर रहा था । मैंने कहा ढांचे खेल के उद्देश्य से किया गया था, जो मूल रूप से कारण मैं उपयोग नहीं हो रहा है typeidया dynamic_cast। हालांकि प्रतिक्रिया के लिए धन्यवाद, मैं इसकी सराहना करता हूं।
मिग्वेलमार्टिन

क्या यह आलोचक घटक-इकाई प्रणाली गेम इंजन के आपके ज्ञान पर आधारित है या यह एक सामान्य C ++ कोड समीक्षा है?
डेन

1
@ जब मुझे लगता है कि किसी भी चीज़ से ज्यादा एक डिजाइन मुद्दा है।
मिगेल.martin

10

प्रबंधक कक्षाओं के साथ समस्या यह है कि एक प्रबंधक कुछ भी कर सकता है। आप कक्षा का नाम नहीं पढ़ सकते हैं और यह जान सकते हैं कि कक्षा क्या करती है। EntityManager .... मुझे पता है कि यह Entities के साथ कुछ करता है लेकिन कौन जानता है। SystemManager ... हाँ, यह सिस्टम का प्रबंधन करता है। यह बहुत मददगार है।

मेरा अनुमान है कि आपके प्रबंधक वर्ग शायद बहुत सारी चीजें करते हैं। मैं शर्त लगाता हूं कि यदि आप उन चीजों को निकट से संबंधित कार्यात्मक टुकड़ों में विभाजित करते हैं, तो आप संभवतः कार्यात्मक नामों से संबंधित वर्ग के नाम के साथ आ पाएंगे, जो किसी को भी बता सकते हैं कि वे कक्षा के नाम को देखकर क्या करते हैं। यह डिजाइन को बनाए रखने और समझने में बहुत आसान बना देगा।


1
+1 और न केवल वे कुछ भी कर सकते हैं, काफी लंबे समय तक वे करेंगे। लोग "प्रबंधक" विवरणक को रसोई-सिंक / स्विस-सेना-चाकू कक्षाएं बनाने के निमंत्रण के रूप में देखते हैं।
एरिक डायट्रिच

@ एरिक - आह हाँ, मुझे यह जोड़ना चाहिए कि एक अच्छे वर्ग का नाम होना न केवल महत्वपूर्ण है क्योंकि यह किसी को बताता है कि वर्ग क्या कर सकता है; लेकिन समान रूप से वर्ग का नाम भी कहता है कि वर्ग को क्या नहीं करना चाहिए।
डंक

3

मैं वास्तव में नहीं लगता कि Managerइस संदर्भ में इतना बुरा है, श्रग। अगर मुझे लगता Managerहै कि यह एक जगह है, तो यह क्षम्य है, यह एक इकाई-घटक प्रणाली के लिए होगा, क्योंकि यह वास्तव में घटकों, संस्थाओं और प्रणालियों के जीवनकाल को " प्रबंधित " कर रहा है। बहुत कम से कम यहाँ एक और अधिक सार्थक नाम है, Factoryजो कहते हैं, जो इस संदर्भ में उतना अर्थ नहीं रखता है क्योंकि एक ईसीएस वास्तव में चीजों को बनाने की तुलना में थोड़ा अधिक करता है।

मुझे लगता है कि आप उपयोग कर सकते हैं Database, जैसे ComponentDatabase। या फिर आप उपयोग कर सकते हैं Components, Systemsऔर Entities(यह क्या मैं व्यक्तिगत रूप से उपयोग करें और मुख्य रूप से सिर्फ इसलिए कि मैं संक्षिप्त पहचानकर्ता की तरह ही यह वैसे बता देते हैं और भी कम जानकारी, शायद, "प्रबंधक" की तुलना में)।

एक हिस्सा जो मुझे थोड़ा सा अनाड़ी लगता है, वह यह है:

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

इससे getपहले कि आप एक इकाई बना सकें, इसके लिए यह पूरी तरह से सामान है और यह महसूस करता है कि डिजाइन कार्यान्वयन विवरण को थोड़ा लीक कर रहा है यदि आपको उन्हें बनाने के लिए इकाई पूल और "प्रबंधकों" के बारे में जानना है। मुझे लगता है कि "पूल" और "मैनेजर" (यदि आप इस नाम से चिपके हुए हैं) जैसी चीजें ईसीएस का कार्यान्वयन विवरण होना चाहिए, न कि ग्राहक के सामने कुछ।

लेकिन पता नहीं, मैं के कुछ बहुत ख़याल से बाहर और सामान्य उपयोग देखा है Manager, Handler, Adapter, और इस तरह के नाम हैं, लेकिन मुझे लगता है कि यह एक यथोचित क्षम्य उपयोग मामला है, जब बात "प्रबंधक" कहा जाता है वास्तव में "प्रबंध" ( "नियंत्रित करने के लिए कौन जिम्मेदार है? "," हैंडलिंग? ") वस्तुओं का जीवनकाल, व्यक्तिगत रूप से बनाने और नष्ट करने और उन तक पहुंच प्रदान करने के लिए जिम्मेदार है। कम से कम मेरे लिए "प्रबंधक" का सबसे सीधा और सहज उपयोग है।

उस ने कहा, आपके नाम में "प्रबंधक" से बचने के लिए एक सामान्य रणनीति सिर्फ "प्रबंधक" भाग को बाहर निकालना और -sअंत में जोड़ना है । : -D उदाहरण के लिए, मान लें कि आपके पास एक है ThreadManagerऔर यह थ्रेड बनाने और उनके बारे में जानकारी प्रदान करने और उन्हें और आगे तक पहुंचाने के लिए जिम्मेदार है, लेकिन यह थ्रेड को पूल नहीं करता है, इसलिए हम इसे कॉल नहीं कर सकते ThreadPool। खैर, उस मामले में, आप इसे कहते हैं Threads। जब भी मैं अकल्पनीय होता हूं तो मैं यही करता हूं।

जब थोड़े "विंडोज एक्सप्लोरर" के समतुल्य समतुल्य को "फाइल मैनेजर" कहा जाता है, तो मुझे याद आया।

यहाँ छवि विवरण दर्ज करें

और मुझे वास्तव में लगता है कि "फ़ाइल प्रबंधक" इस मामले में "विंडोज एक्सप्लोरर" की तुलना में अधिक वर्णनात्मक है, भले ही वहाँ कुछ बेहतर नाम हो जिसमें "प्रबंधक" शामिल नहीं है। तो बहुत कम से कम कृपया अपने नाम के साथ बहुत अधिक फैंसी न लें और "कॉम्पोनेन्ट एक्सप्लॉपर" जैसी चीजों को नाम देना शुरू करें। "ComponentManager" कम से कम मुझे इस बात का एक उचित विचार देता है कि वह चीज क्या करती है जैसा कोई व्यक्ति ईसीएस इंजन के साथ काम करने के लिए करता था।


माना। यदि किसी वर्ग का उपयोग विशिष्ट प्रबंधकीय कर्तव्यों के लिए किया जाता है (बनाना ("काम पर रखना"), नष्ट करना ("फायरिंग"), ओवरसाइट, और "कर्मचारी" / उच्चतर इंटरफ़ेस, तो नाम Managerउपयुक्त है। वर्ग में डिज़ाइन मुद्दे हो सकते हैं, लेकिन नाम। हाजिर है।
जस्टिन टाइम 2
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.