क्या आप खुले-बंद सिद्धांत का लाभ उठाते हैं?


12

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

निम्नलिखित एक सरल, विस्तार योग्य वर्ग पर विचार करें:

class PaycheckCalculator {
    // ...
    protected decimal GetOvertimeFactor() { return 2.0M; }
}

अब कहते हैं, उदाहरण के लिए, कि OvertimeFactorपरिवर्तन 1.5। चूंकि उपरोक्त वर्ग को विस्तारित करने के लिए डिज़ाइन किया गया था, इसलिए मैं आसानी से उप-वर्ग कर सकता हूं और एक अलग वापसी कर सकता हूं OvertimeFactor

लेकिन ... वर्ग के विस्तार और OCP के पालन के लिए डिज़ाइन किए जाने के बावजूद, मैं प्रश्न में विधि को उप-वर्ग करने और ओवरराइड करने के बजाय प्रश्न में एकल विधि को संशोधित करूंगा और फिर अपने IoC कंटेनर में अपनी वस्तुओं को फिर से वायर करूंगा।

परिणामस्वरूप, मैंने OCP को पूरा करने के लिए क्या प्रयास किया है, के एक हिस्से का उल्लंघन किया है। ऐसा लगता है कि मैं सिर्फ आलसी हो रहा हूं क्योंकि ऊपर थोड़ा आसान है। क्या मैं OCP को गलत समझ रहा हूँ? क्या मुझे वास्तव में कुछ अलग करना चाहिए? क्या आप OCP के लाभों का अलग-अलग लाभ उठाते हैं?

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

जवाबों:


10

यदि आप आधार वर्ग को संशोधित कर रहे हैं तो यह वास्तव में बंद नहीं है!

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

वास्तव में कक्षा को बंद करने के लिए लेकिन आपको एक वैकल्पिक स्रोत से ओवरटाइम फैक्टर को पुनः प्राप्त करना चाहिए (हो सकता है फ़ाइल कॉन्फ़िगर करें) या एक आभासी विधि साबित करना जिसे ओवरराइड किया जा सकता है?

यदि वर्ग वास्तव में बंद था, तो आपके परिवर्तन के बाद कोई भी परीक्षण मामले विफल नहीं होंगे (यह मानकर कि आपके सभी परीक्षण मामलों के साथ आपके पास 100% कवरेज है) और मैं मानूंगा कि एक परीक्षण मामला है जो जांच करता है GetOvertimeFactor() == 2.0M

इंजीनियर से अधिक मत करो

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

बंद सिद्धांत आपको ऑब्जेक्ट को फिर से इंजीनियरिंग करने से रोकता नहीं है। यह आपको वर्तमान में परिभाषित सार्वजनिक इंटरफ़ेस को आपके ऑब्जेक्ट में बदलने से पहले से पूर्व-निर्धारित करता है ( संरक्षित सदस्य सार्वजनिक इंटरफ़ेस का हिस्सा हैं)। आप तब भी अधिक कार्यक्षमता जोड़ सकते हैं जब तक कि पुरानी कार्यक्षमता भंग न हो।


"बंद सिद्धांत आपको ऑब्जेक्ट को फिर से इंजीनियरिंग करने से रोकता नहीं है।" असल में, यह करता है । यदि आप उस पुस्तक को पढ़ते हैं जहां ओपन-क्लोज्ड सिद्धांत पहले प्रस्तावित किया गया था, या लेख जिसमें "ओसीपी" संक्षिप्त परिचय दिया गया था, तो आप देखेंगे कि यह कहता है कि "किसी को भी स्रोत कोड में बदलाव करने की अनुमति नहीं है" (बग को छोड़कर) फिक्स)।
रोजेरियो

@ रोजेरियो: यह सच हो सकता है (1988 में वापस)। लेकिन वर्तमान परिभाषा (1990 में जब ओओ लोकप्रिय हो गई थी) सभी एक निरंतर सार्वजनिक इंटरफ़ेस बनाए रखने के बारे में है। During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other. en.wikipedia.org/wiki/Open/closed_principle
मार्टिन

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

3

तो ओपन क्लोज्ड सिद्धांत एक गेटा है ... खासकर यदि आप इसे उसी समय लागू करने की कोशिश करते हैं जैसे कि YAGNI । मैं एक ही समय में दोनों का पालन कैसे करूं? तीन का नियम लागू करें । पहली बार जब आप बदलाव करते हैं, तो इसे सीधे करें। और दूसरी बार भी। तीसरी बार, यह अमूर्त करने का समय है जो बदल जाता है।

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


2

आपके द्वारा पोस्ट किए गए उदाहरण में, ओवरटाइम कारक एक चर या एक स्थिर होना चाहिए। * (जावा उदाहरण)

class PaycheckCalculator {
   float overtimeFactor;

   protected float setOvertimeFactor(float overtimeFactor) {
      this.overtimeFactor = overtimeFactor;
   }

   protected float getOvertimeFactor() {
      return overtimeFactor;
   }
}

या

class PaycheckCalculator {
   public static final float OVERTIME_FACTOR = 1.5f;
}

फिर जब आप वर्ग का विस्तार करते हैं, तो कारक सेट या ओवरराइड करें। "मैजिक नंबर" केवल एक बार दिखना चाहिए। यह OCP और DRY (अपने आप को दोहराएं नहीं) की शैली में बहुत अधिक है, क्योंकि पहली विधि का उपयोग करने पर एक अलग कारक के लिए पूरी नई कक्षा बनाना आवश्यक नहीं है, और केवल एक मुहावरेदार में स्थिरांक बदलने के लिए दूसरे स्थान पर।

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

दूसरा उन मामलों में उपयोगी है जहां निरंतर बदलने की संभावना नहीं है, लेकिन कई स्थानों पर इसका उपयोग किया जाता है। बदलने के लिए एक निरंतर होने (उस अप्रत्याशित घटना में जो यह करता है) उसे पूरे स्थान पर स्थापित करने या संपत्ति फ़ाइल से प्राप्त करने की तुलना में बहुत आसान है।

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


यह एक डेटा परिवर्तन है, न कि एक कोड परिवर्तन। ओवरटाइम दर हार्ड कोडित नहीं होनी चाहिए थी।
जिम सी।

आपको लगता है कि आपका गेट और आपका सेट पीछे की ओर है।
मेसन व्हीलर

ओह! परीक्षण किया जाना चाहिए ...
माइकल के

2

मैं वास्तव में आपके उदाहरण को OCP के महान प्रतिनिधित्व के रूप में नहीं देखता। मुझे लगता है कि नियम वास्तव में इसका मतलब है:

जब आप एक सुविधा जोड़ना चाहते हैं, तो आपको केवल एक वर्ग जोड़ना होगा और आपको किसी अन्य वर्ग को संशोधित करने की आवश्यकता नहीं होनी चाहिए (लेकिन संभवतः एक विन्यास)।

नीचे एक खराब कार्यान्वयन। हर बार जब आप कोई गेम जोड़ते हैं तो आपको GamePlayer क्लास को संशोधित करना होगा।

class GamePlayer
{
   public void PlayGame(string game)
   {
      switch(game)
      {
          case "Poker":
              PlayPoker();
              break;

          case "Gin": 
              PlayGin();
              break;

          ...
      }
   }

   ...
}

GamePlayer वर्ग को कभी भी संशोधित करने की आवश्यकता नहीं होनी चाहिए

class GamePlayer
{
    ...

    public void PlayGame(string game)
    {
        Game g = GameFactory.GetByName(game); 
        g.Play();   
    }

    ...
}

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

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

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

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


1

इस विशेष उदाहरण में, आपके पास "मैजिक वैल्यू" के रूप में जाना जाता है। अनिवार्य रूप से एक कठिन कोडित मूल्य जो समय के साथ बदल सकता है या नहीं। मैं उस पहेली को संबोधित करने का प्रयास करने जा रहा हूं जिसे आप उदारतापूर्वक व्यक्त करते हैं, लेकिन यह उस प्रकार का एक उदाहरण है जहां एक वर्ग में मूल्य बदलने की तुलना में एक उपवर्ग का निर्माण अधिक काम है।

संभावना से अधिक, आपने अपने वर्ग पदानुक्रम में बहुत जल्दी व्यवहार निर्दिष्ट किया है।

मान लीजिए कि हमारे पास है PaycheckCalculatorOvertimeFactorसे अधिक होने की संभावना कर्मचारी के बारे में जानकारी के बंद keyed किया जाएगा। एक प्रति घंटा कर्मचारी एक ओवरटाइम बोनस का आनंद ले सकता है, जबकि एक वेतनभोगी कर्मचारी को कुछ भी भुगतान नहीं किया जाएगा। फिर भी, कुछ वेतनभोगी कर्मचारियों को उस अनुबंध के कारण सीधे समय मिल जाएगा जो वे काम कर रहे थे। आप यह तय कर सकते हैं कि भुगतान परिदृश्यों के कुछ ज्ञात वर्ग हैं, और यही आप अपने तर्क का निर्माण करेंगे।

बेस PaycheckCalculatorक्लास में आप इसे अमूर्त बनाते हैं, और आपके द्वारा अपेक्षित विधियों को निर्दिष्ट करते हैं। मुख्य गणनाएं समान हैं, यह सिर्फ इतना है कि कुछ कारकों की गणना अलग तरीके से की जाती है। आपका HourlyPaycheckCalculatorतब getOvertimeFactorविधि लागू करेगा और 1.5 या 2.0 वापस कर देगा क्योंकि आपका मामला हो सकता है। आपका 1.0 वापस StraightTimePaycheckCalculatorकरने के getOvertimeFactorलिए लागू होगा । अंत में एक तीसरा कार्यान्वयन होगा NoOvertimePaycheckCalculatorजो getOvertimeFactor0 को वापस करने के लिए लागू होगा ।

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.