एकल-जिम्मेदारी और कस्टम डेटा प्रकार


10

पिछले महीनों में मैंने यहां के लोगों के लिए एसई और अन्य साइटों पर मेरे कोड के बारे में कुछ रचनात्मक आलोचना की पेशकश की है। एक चीज है जो लगभग हर बार पॉपिंग करती है और मैं अभी भी उस सिफारिश से सहमत नहीं हूं; : P मैं यहाँ इसकी चर्चा करना चाहता हूँ और शायद चीजें मेरे लिए स्पष्ट हो जाएँगी।

यह एकल-जिम्मेदारी सिद्धांत (एसआरपी) के बारे में है। मूल रूप से, मेरे पास एक डेटा वर्ग है, Fontजो न केवल डेटा में हेरफेर करने के लिए, बल्कि इसे लोड करने के लिए भी कार्य करता है। मुझे बताया गया है कि दोनों को अलग होना चाहिए, लोडिंग फ़ंक्शंस को फ़ैक्टरी क्लास के अंदर रखा जाना चाहिए; मुझे लगता है कि यह एसआरपी की गलत व्याख्या है ...

मेरा फ़ॉन्ट कक्षा से एक टुकड़ा

class Font
{
  public:
    bool isLoaded() const;
    void loadFromFile(const std::string& file);
    void loadFromMemory(const void* buffer, std::size_t size);
    void free();

    void some();
    void another();
};

सुझाया गया डिज़ाइन

class Font
{
  public:
    void some();
    void another();
};


class FontFactory
{
  public:
    virtual std::unique_ptr<Font> createFromFile(...) = 0;
    virtual std::unique_ptr<Font> createFromMemory(...) = 0;
};

सुझाया गया डिज़ाइन कथित रूप से SRP का अनुसरण करता है, लेकिन मैं असहमत हूं - मुझे लगता है कि यह बहुत दूर चला जाता है। Fontवर्ग लंबे समय तक आत्मनिर्भर (यह कारखाना के बिना बेकार है), और नहीं है FontFactoryकी जरूरत है संसाधन है, जो शायद दोस्ती या सार्वजनिक टिककर खेल के माध्यम से किया जाता है के कार्यान्वयन है, जो आगे के कार्यान्वयन का पर्दाफाश के बारे में विवरण जानने के लिए Font। मुझे लगता है कि यह खंडित जिम्मेदारी का मामला है ।

यहाँ मुझे लगता है कि मेरा दृष्टिकोण बेहतर है:

  • Fontआत्मनिर्भर है - आत्मनिर्भर होने के कारण, इसे समझना और बनाए रखना आसान है। इसके अलावा, आप किसी और चीज को शामिल किए बिना वर्ग का उपयोग कर सकते हैं। यदि, हालांकि, आपको लगता है कि आपको संसाधनों के एक अधिक जटिल प्रबंधन की आवश्यकता है (एक कारखाना) तो आप आसानी से ऐसा कर सकते हैं (बाद में मैं अपने कारखाने के बारे में बात करूँगा ResourceManager<Font>)।

  • मानक पुस्तकालय का अनुसरण करता है - मेरा मानना ​​है कि उपयोगकर्ता-परिभाषित प्रकारों को उस संबंधित भाषा में मानक प्रकारों के व्यवहार को कॉपी करने के लिए यथासंभव प्रयास करना चाहिए। std::fstreamआत्मनिर्भर है और यह की तरह कार्य प्रदान करता openहै और close। मानक पुस्तकालय के बाद का मतलब है कि चीजों को करने का एक और तरीका सीखने के लिए प्रयास करने की आवश्यकता नहीं है। इसके अलावा, आम तौर पर बोलते हुए, C ++ मानक समिति शायद यहां किसी की तुलना में डिजाइन के बारे में अधिक जानती है, इसलिए यदि कभी संदेह हो, तो वे जो करते हैं उसे कॉपी करें।

  • परीक्षणशीलता - कुछ गलत हो जाता है, समस्या कहां हो सकती है? - क्या यह तरीका Fontअपना डेटा संभालता है या डेटा FontFactoryलोड करने का तरीका है ? तुम सच में नहीं जानते। कक्षाएं आत्मनिर्भर होने से यह समस्या कम हो जाती है: आप Fontअलगाव में परीक्षण कर सकते हैं । यदि आपको कारखाने का परीक्षण करना है और आप जानते हैं कि Fontठीक काम करता है, तो आपको यह भी पता होगा कि जब भी कोई समस्या होती है तो उसे कारखाने के अंदर होना चाहिए।

  • यह संदर्भ अज्ञेय है - (यह मेरी पहली बात के साथ थोड़ा प्रतिच्छेद Fontकरता है ।) अपनी बात करता है और इस बारे में कोई धारणा नहीं बनाता है कि आप इसका उपयोग कैसे करेंगे: आप इसका उपयोग किसी भी तरह से कर सकते हैं। उपयोगकर्ता को किसी कारखाने का उपयोग करने के लिए मजबूर करना कक्षाओं के बीच युग्मन को बढ़ाता है।

मेरा भी एक कारखाना है

(क्योंकि डिजाइन Fontमुझे अनुमति देता है।)

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

template<class T>
class ResourceManager
{
  public:
    ResourcePtr<T> acquire(const std::string& file);
    ResourcePtr<T> acquire(const void* buffer, std::size_t size);
};

यहां बताया गया है कि प्रबंधक का उपयोग कैसे किया जा सकता है। ध्यान दें कि यह एक कारखाने के रूप में मूल रूप से उपयोग किया जाता है।

void test(ResourceManager<Font>* rm)
{
    // The same file isn't loaded twice into memory.
    // I can still have as many Fonts using that file as I want, though.
    ResourcePtr<Font> font1 = rm->acquire("fonts/arial.ttf");
    ResourcePtr<Font> font2 = rm->acquire("fonts/arial.ttf");

    // Print something with the two fonts...
}

जमीनी स्तर...

(यह एक tl डालना चाहते हैं; यहाँ डॉ; लेकिन मैं एक के बारे में नहीं सोच सकता ।:))
ठीक है, वहाँ तुम्हारे पास है, मैं अपने मामले में सबसे अच्छा के रूप में मैं कर सकता था। कृपया आपके द्वारा किए गए किसी भी काउंटर-तर्क को पोस्ट करें और यह भी कि आपको लगता है कि सुझाए गए डिज़ाइन का मेरे खुद के डिज़ाइन से अधिक है। मूल रूप से, मुझे दिखाने की कोशिश करें कि मैं गलत हूं। :)


2
याद दिलाता है कि मार्टिन फाउलर के ActiveRecord बनाम DataMapper
उपयोगकर्ता

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


@ मुझे उस प्रस्तुति के बारे में पता है, मेरे पास इसका एक बुकमार्क था ( वीडियो )। :) लेकिन मुझे समझ में नहीं आ रहा है कि आप अपनी अन्य टिप्पणी में क्या कह रहे हैं ...
पॉल

1
@rwong क्या यह पहले से ही एक लाइनर नहीं है? आपको केवल एक पंक्ति की आवश्यकता है, चाहे आप सीधे एक फ़ॉन्ट लोड करें या संसाधन प्रबंधक के माध्यम से। और अगर उपयोगकर्ता शिकायत करते हैं तो मुझे RM को फिर से लागू करने से क्या रोकता है?
पॉल

जवाबों:


7

मेरी राय में उस कोड के साथ कुछ भी गलत नहीं है, यह वही करता है जो आपको समझदार और उचित तरीके से बनाए रखने के लिए बहुत आसान है।

हालाँकि , आपके पास इस कोड के साथ समस्या यह है कि यदि आप चाहते हैं कि यह कुछ और भी करे तो आपको यह सब बदलना होगा

SRP की बात यह है कि अगर आपके पास एक एकल घटक 'CompA' है जो एल्गोरिथम A () करता है और आपको एल्गोरिथ्म A () बदलने की आवश्यकता है तो आपको 'CompB' भी नहीं बदलना चाहिए।

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

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


1
आह, मैं देखता हूं: यदि मैं एक फ़ॉन्ट लोड करने के लिए एक और तरीका जोड़ता हूं, तो मुझे प्रबंधक को एक और अधिग्रहित फ़ंक्शन और एक और लोड फ़ंक्शन को संसाधन में जोड़ना होगा। दरअसल, यह एक नुकसान है। हालांकि, यह नया स्रोत क्या हो सकता है, इसके आधार पर, आपको संभवतः डेटा को अलग तरह से संभालना होगा (TTFs एक बात है, फ़ॉन्ट स्प्राइट दूसरे हैं), इसलिए आप वास्तव में यह अनुमान नहीं लगा सकते हैं कि एक निश्चित डिज़ाइन कितना लचीला होने वाला है। मैं आपकी बात देखता हूँ, हालाँकि।
पॉल

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

महान प्रश्न और महान उत्तर, और सबसे अच्छी बात यह है कि कई डेवलपर्स इससे सीख सकते हैं। यही कारण है कि मुझे यहां घूमना बहुत पसंद है :)। ओह और इसलिए मेरी टिप्पणी पूरी तरह से निरर्थक नहीं है, एसआरपी थोड़ा मुश्किल हो सकता है क्योंकि आपको खुद से 'क्या होगा' पूछना होगा, जो काउंटर कर सकता है: 'समय से पहले अनुकूलन सभी बुराई की जड़ है' या ' YAGNI के दर्शन। वहाँ एक काला और सफेद जवाब कभी नहीं है!
मार्टिज़न वर्बर्ग

0

एक बात जो मुझे आपकी कक्षा के बारे में बताती है, वह है आपके पास loadFromMemoryऔर loadFromFileविधियाँ। आदर्श रूप में, आपके पास केवल loadFromMemoryविधि होनी चाहिए ; एक फॉन्ट को यह ध्यान नहीं रखना चाहिए कि मेमोरी में डेटा कैसे आया। एक और बात यह है कि आपको लोड और freeतरीकों के बजाय कंस्ट्रक्टर / डिस्ट्रक्टर का उपयोग करना चाहिए । इस प्रकार, loadFromMemoryबन जाएगा Font(const void *buf, int len)और free()बन जाएगा ~Font()


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