एलएसपी बनाम ओसीपी / लिस्कोव प्रतिस्थापन वीएस ओपन क्लोज


48

मैं OOP के ठोस सिद्धांतों को समझने की कोशिश कर रहा हूं और मैं इस निष्कर्ष पर पहुंचा हूं कि एलएसपी और ओसीपी में कुछ समानताएं हैं (यदि अधिक कहने के लिए नहीं)।

खुला / बंद सिद्धांत बताता है कि "सॉफ्टवेयर इकाइयां (कक्षाएं, मॉड्यूल, फ़ंक्शन, आदि) विस्तार के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए बंद हो गईं"।

एलएसपी सरल शब्दों में कहता है कि किसी भी उदाहरण को किसी भी उदाहरण से Fooप्रतिस्थापित किया जा सकता है Barजिसमें से लिया गया है Fooऔर कार्यक्रम उसी तरह काम करेगा।

मैं एक समर्थक ओओपी प्रोग्रामर नहीं हूं, लेकिन मुझे ऐसा लगता है कि एलएसपी केवल तभी संभव है Bar, जब इससे व्युत्पन्न Fooकुछ भी इसमें नहीं बदलता है, लेकिन केवल इसका विस्तार करता है। इसका मतलब है कि विशेष रूप से कार्यक्रम में एलएसपी केवल तभी सच है जब ओसीपी सच है और ओसीपी केवल तभी सच है जब एलएसपी सच है। इसका मतलब है कि वे समान हैं।

यदि मैं गलत हूं तो मुझे सही करों। मैं वास्तव में इन विचारों को समझना चाहता हूं। एक उत्तर के लिए महान धन्यवाद।


4
यह दोनों अवधारणाओं की एक बहुत ही संकीर्ण व्याख्या है। खुला / बंद अभी भी एलएसपी का उल्लंघन किया जा सकता है। आयत / वर्ग या दीर्घवृत्त / वृत्त उदाहरण अच्छे चित्र हैं। दोनों OCP का पालन करते हैं, फिर भी दोनों LSP का उल्लंघन करते हैं।
जोएल एथरटन

1
दुनिया (या कम से कम इंटरनेट) इस पर भ्रमित है। kirkk.com/modularity/2009/12/solid-principles-of-class-design । इस आदमी का कहना है कि एलएसपी का उल्लंघन भी OCP का उल्लंघन है। और फिर पेज 156 पर "सॉफ्टवेयर इंजीनियरिंग डिजाइन: थ्योरी एंड प्रैक्टिस" पुस्तक में लेखक कुछ ऐसा उदाहरण देता है जो OCP का पालन करता है लेकिन LSP का उल्लंघन करता है। मैंने इस पर ध्यान दिया है।
मनोज आर

@JoelEtherton वे जोड़े केवल एलएसपी का उल्लंघन करते हैं यदि वे पारस्परिक हैं। अपरिवर्तनीय मामले में, पाने Squareसे RectangleLSP उल्लंघन नहीं करता। (लेकिन यह शायद अभी भी अपरिवर्तनीय मामले में खराब डिजाइन है क्योंकि आपके पास वर्ग Rectangleएस हो सकते हैं Squareजो गणित से मेल नहीं खाते हैं)
कोडइन्कॉउंस

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

जवाबों:


119

गोश, OCP और LSP के बारे में कुछ अजीब गलतफहमियां हैं और कुछ कुछ शब्दावली और भ्रमित उदाहरणों के बेमेल होने के कारण हैं। यदि आप उन्हें उसी तरह से लागू करते हैं तो दोनों सिद्धांत केवल "समान बात" हैं। पैटर्न आम तौर पर कुछ अपवादों के साथ एक या दूसरे तरीके से सिद्धांतों का पालन करते हैं।

मतभेदों को और नीचे समझाया जाएगा लेकिन पहले हमें सिद्धांतों में डुबकी लगाने दें:

ओपन-क्लोज्ड सिद्धांत (OCP)

अंकल बॉब के अनुसार :

आपको इसे संशोधित किए बिना, कक्षाओं के व्यवहार का विस्तार करने में सक्षम होना चाहिए।

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

मूल बर्ट्रैंड मेयर से 1988 में आया था:

सॉफ्टवेयर इकाइयां (कक्षाएं, मॉड्यूल, फ़ंक्शन, आदि) विस्तार के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए बंद।

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

// Context is closed for modifications. Meaning you are
// not supposed to change the code here.
public class Context {

    // Context is however open for extension through
    // this private field
    private IBehavior behavior;

    // The context calls the behavior in this public 
    // method. If you want to change this you need
    // to implement it in the IBehavior object
    public void doStuff() {
        if (this.behavior != null)
            this.behavior.doStuff();
    }

    // You can dynamically set a new behavior at will
    public void setBehavior(IBehavior behavior) {
        this.behavior = behavior;
    }
}

// The extension point looks like this and can be
// subclassed/implemented
public interface IBehavior {
    public void doStuff();
}

ऊपर के उदाहरण में Contextहै बंद कर दिया आगे संशोधन के लिए। अधिकांश प्रोग्रामर शायद इसे बढ़ाने के लिए कक्षा को उप-वर्ग में डालना चाहते हैं, लेकिन यहां हम नहीं मानते क्योंकि यह व्यवहार है कि यह इंटरफ़ेस को लागू करने वाली किसी भी चीज़ के माध्यम से बदला जा सकता IBehaviorहै।

यानी संदर्भ वर्ग संशोधन के लिए बंद है, लेकिन विस्तार के लिए खुला है । यह वास्तव में एक और बुनियादी सिद्धांत का पालन करता है क्योंकि हम व्यवहार को विरासत के बजाय वस्तु संरचना के साथ रख रहे हैं:

" क्लास ' इनहेरिटेंस ' पर ' फेवर' ऑब्जेक्ट कंपोज़िशन ।" (गैंग ऑफ़ फोर 1995: 20)

मैं पाठक को उस सिद्धांत पर पढ़ने दूंगा क्योंकि यह इस प्रश्न के दायरे से बाहर है। उदाहरण के साथ जारी रखने के लिए, मान लें कि हमारे पास IBehavior इंटरफ़ेस के निम्नलिखित कार्यान्वयन हैं:

public class HelloWorldBehavior implements IBehavior {
    public void doStuff() {
        System.println("Hello world!");
    }
}

public class GoodByeBehavior implements IBehavior {
    public void doStuff() {
        System.out.println("Good bye cruel world!");
    }
}

इस पैटर्न का उपयोग करके हम रनटाइम पर संदर्भ के व्यवहार setBehaviorको विस्तार बिंदु के रूप में विधि के माध्यम से संशोधित कर सकते हैं ।

// in your main method
Context c = new Context();

c.setBehavior(new HelloWorldBehavior());
c.doStuff();
// prints out "Hello world!"

c.setBehavior(new GoodByeBehavior());
c.doStuff();
// prints out "Good bye cruel world!"

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

वंशानुक्रम के बजाय मिक्सिन्स के साथ विस्तार

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

यहाँ एक मिश्रण का एक जावास्क्रिप्ट उदाहरण दिया गया है जो एंकर के लिए एक सरल HTML टेम्पलेट प्रस्तुत करता है:

// The mixin, provides a template for anchor HTML elements, i.e. <a>
var LinkMixin = {
    render: function() {
        return '<a href="' + this.link +'">'
            + this.content 
            + '</a>;
    }
}

// Constructor for a youtube link
var YoutubeLink = function(content, youtubeId) {
    this.content = content;
    this.setLink(this.youtubeId);
};
// Methods are added to the prototype
YoutubeLink.prototype = {
    setLink: function(youtubeid) {
        this.link = 'http://www.youtube.com/watch?v=' + youtubeid;
    }
};
// Extend YoutubeLink prototype with the LinkMixin using
// underscore/lodash extend
_.extend(YoutubeLink.protoype, LinkMixin);

// When used:
var ytLink = new YoutubeLink("Cool Movie!", "idOaZpX8lnA");

console.log(ytLink.render());
// will output: 
// <a href="http://www.youtube.com/watch?=vidOaZpX8lnA">Cool Movie!</a>

विचार वस्तुओं को गतिशील रूप से विस्तारित करना है और इसका लाभ यह है कि ऑब्जेक्ट पूरी तरह से अलग डोमेन में होने पर भी तरीकों को साझा कर सकते हैं। उपरोक्त मामले में आप आसानी से अपने विशिष्ट कार्यान्वयन को बढ़ाकर अन्य प्रकार के HTML एंकर बना सकते हैं LinkMixin

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

ध्यान दें कि इस पद्धति के साथ कई उत्तराधिकार करना संभव है क्योंकि अधिकांश extendकार्यान्वयन कई वस्तुओं में मिश्रण कर सकते हैं:

_.extend(MyClass, Mixin1, Mixin2 /* [, ...] */);

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

लिस्कोव के प्रतिस्थापन सिद्धांत (एलएसपी)

अंकल बॉब इसे बस द्वारा परिभाषित करता है:

व्युत्पन्न वर्गों को उनके आधार वर्गों के लिए प्रतिस्थापित किया जाना चाहिए।

यह सिद्धांत पुराना है, वास्तव में अंकल बॉब की परिभाषा सिद्धांतों को अलग नहीं करती है क्योंकि एलएसपी अभी भी ओसीपी से निकटता से संबंधित है, इस तथ्य से कि, ऊपर की रणनीति उदाहरण में, एक ही सुपरपाइप का उपयोग किया जाता है ( IBehavior)। तो इसे बारबरा लिस्कॉव की मूल परिभाषा पर गौर करें और देखें कि क्या हम इस सिद्धांत के बारे में कुछ और जान सकते हैं जो गणितीय सिद्धांत की तरह दिखता है:

यहाँ क्या तलाश है निम्नलिखित प्रतिस्थापन संपत्ति की तरह कुछ है: प्रत्येक वस्तु के लिए तो o1प्रकार के Sएक वस्तु है o2प्रकार का Tऐसा है कि सभी कार्यक्रमों के लिए Pके रूप में परिभाषित T, के व्यवहार Pमें कोई बदलाव नहीं है, जब o1दिया जाता है o2तो Sकी एक उप-प्रकार है T

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

  • उसी तरह से गणना करने की जरूरत है,
  • एक ही व्यवहार है, और
  • अन्यथा किसी तरह पूरी तरह से अलग हैं

... तो वस्तुओं को एक ही "प्रकार" के रूप में माना जाता है और यह वास्तव में कार्यक्रम के लिए मायने नहीं रखता है। यह मूलत: बहुरूपता हैसामान्य अर्थों में; यदि आप इसका उपयोग कर रहे हैं, तो आपको वास्तविक उपप्रकार जानने की आवश्यकता नहीं है। OCP इस बारे में कुछ भी स्पष्ट नहीं कहता है। यह वास्तव में एक डिजाइन गलती को इंगित करता है जो अधिकांश नौसिखिए प्रोग्रामर करते हैं:

जब भी आप किसी वस्तु के उपप्रकार की जाँच करने का आग्रह कर रहे हैं, तो आप सबसे अधिक संभावना है कि आप इसे गलत कर रहे हैं।

ठीक है, इसलिए यह हर समय गलत नहीं हो सकता है, लेकिन अगर आपके पास कुछ प्रकार की जाँच करने की इच्छा है instanceofया आप इसके साथ काम कर रहे हैं, तो आप इस कार्यक्रम को अपने लिए थोड़ा और जटिल बना सकते हैं, जितना कि यह आवश्यक है। पर यह मामला हमेशा नहीं होता; काम करने के लिए त्वरित और गंदे हैक्स मेरे दिमाग में बनाने के लिए एक ठीक रियायत है यदि समाधान काफी छोटा है, और यदि आप निर्दयतापूर्ण रीफ़ैक्टरिंग का अभ्यास करते हैं , तो इसे बदलने की मांग करने के बाद इसमें सुधार हो सकता है।

वास्तविक समस्या के आधार पर, इस "डिज़ाइन गलती" के आसपास के तरीके हैं:

  • सुपर क्लास पूर्वापेक्षाओं को नहीं बुला रहा है, कॉल करने वाले को ऐसा करने के लिए मजबूर कर रहा है।
  • सुपर क्लास को एक सामान्य विधि याद आ रही है जिसे कॉल करने वाले को चाहिए।

ये दोनों सामान्य कोड डिज़ाइन "गलतियाँ" हैं। ऐसे कई रिफ्लेक्टर हैं जिन्हें आप कर सकते हैं, जैसे कि पुल-अप विधि या रिफ्लेक्टर जैसे विस्कोस पैटर्न

मैं वास्तव में विजिटर पैटर्न को बहुत पसंद करता हूं क्योंकि यह बड़े अगर-स्टेटमेंट स्पेगेटी की देखभाल कर सकता है और मौजूदा कोड पर आपके विचार से इसे लागू करना सरल है। कहें कि हमारे पास निम्नलिखित संदर्भ हैं:

public class Context {

    public void doStuff(string query) {

        // outcome no. 1
        if (query.Equals("Hello")) {
            System.out.println("Hello world!");
        } 

        // outcome no. 2
        else if (query.Equals("Bye")) {
            System.out.println("Good bye cruel world!");
        }

        // a change request may require another outcome...

    }

}

// usage:
Context c = new Context();

c.doStuff("Hello");
// prints "Hello world"

c.doStuff("Bye");
// prints "Bye"

इफ-स्टेटमेंट के परिणामों को अपने स्वयं के आगंतुकों में अनुवाद किया जा सकता है क्योंकि प्रत्येक को कुछ निर्णय और चलाने के लिए कुछ कोड पर निर्भर करता है। हम इन्हें इस तरह से निकाल सकते हैं:

public interface IVisitor {
    public bool canDo(string query);
    public void doStuff();
}

// outcome 1
public class HelloVisitor implements IVisitor {
    public bool canDo(string query) {
        return query.Equals("Hello");
    }
    public void doStuff() {
         System.out.println("Hello World");
    }
}

// outcome 2
public class ByeVisitor implements IVisitor {
    public bool canDo(string query) {
        return query.Equals("Bye");
    }
    public void doStuff() {
        System.out.println("Good bye cruel world");
    }
}

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

public class Context {
    private ArrayList<IVisitor> visitors = new ArrayList<IVisitor>();

    public Context() {
        visitors.add(new HelloVisitor());
        visitors.add(new ByeVisitor());
    }

    // instead of if-statements, go through all visitors
    // and use the canDo method to determine if the 
    // visitor object is the right one to "visit"
    public void doStuff(string query) {
        for(IVisitor visitor : visitors) {
            if (visitor.canDo(query)) {
                visitor.doStuff();
                break;
                // or return... it depends if you have logic 
                // after this foreach loop
            }
        }
    }

    // dynamically adds new visitors
    public void addVisitor(IVisitor visitor) {
        if (visitor != null)
            visitors.add(visitor);
    }
}

दोनों पैटर्न OCP और LSP का अनुसरण करते हैं, हालांकि वे दोनों उनके बारे में अलग-अलग बातें बता रहे हैं। तो कोड कैसे दिखता है अगर यह सिद्धांतों में से एक का उल्लंघन करता है?

एक सिद्धांत का उल्लंघन करना लेकिन दूसरे का अनुसरण करना

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

OCP का अनुसरण करता है लेकिन LSP का नहीं

कहते हैं कि हमारे पास दिए गए कोड हैं:

public interface IPerson {}

public class Boss implements IPerson {
    public void doBossStuff() { ... }
}

public class Peon implements IPerson {
    public void doPeonStuff() { ... }
}

public class Context {
    public Collection<IPerson> getPersons() { ... }
}

कोड का यह टुकड़ा खुले-बंद सिद्धांत का पालन करता है। यदि हम संदर्भ GetPersonsविधि को कॉल कर रहे हैं , तो हम सभी व्यक्तियों को अपने स्वयं के कार्यान्वयन के साथ एक गुच्छा देंगे। इसका मतलब है कि IPerson संशोधन के लिए बंद है, लेकिन विस्तार के लिए खुला है। हालाँकि जब हमें इसका उपयोग करना होता है तो चीजें एक अंधेरा मोड़ लेती हैं:

// in some routine that needs to do stuff with 
// a collection of IPerson:
Collection<IPerson> persons = context.getPersons();
for (IPerson person : persons) {
    // now we have to check the type... :-P
    if (person instanceof Boss) {
        ((Boss) person).doBossStuff();
    }
    else if (person instanceof Peon) {
        ((Peon) person).doPeonStuff();
    }
}

आपको टाइप चेकिंग और टाइप कन्वर्सेशन करना है! याद रखें कि मैंने ऊपर कैसे उल्लेख किया है कि किस प्रकार की जाँच एक बुरी बात है ? अरे नहीं! लेकिन डर नहीं, जैसा कि ऊपर बताया गया है कि या तो कुछ पुल-अप रिफैक्टरिंग करते हैं या विज़िटर पैटर्न को लागू करते हैं। इस मामले में हम एक सामान्य विधि को जोड़ने के बाद बस एक पुल अप रिफैक्टरिंग कर सकते हैं:

public class Boss implements IPerson {
    // we're adding this general method
    public void doStuff() {
        // that does the call instead
        this.doBossStuff();
    }
    public void doBossStuff() { ... }
}


public interface IPerson {
    // pulled up method from Boss
    public void doStuff();
}

// do the same for Peon

लाभ यह है कि आपको एलएसपी के बाद अब सटीक प्रकार जानने की जरूरत नहीं है:

// in some routine that needs to do stuff with 
// a collection of IPerson:
Collection<IPerson> persons = context.getPersons();
for (IPerson person : persons) {
    // yay, no type checking!
    person.doStuff();
}

एलएसपी का पालन करता है लेकिन ओसीपी का नहीं

कुछ कोड को देखते हैं जो एलएसपी का अनुसरण करते हैं लेकिन ओसीपी का नहीं, यह एक तरह से विरोधाभासी है लेकिन इस पर मेरे साथ सहन करना बहुत ही सूक्ष्म गलती है:

public class LiskovBase {
    public void doStuff() {
        System.out.println("My name is Liskov");
    }
}

public class LiskovSub extends LiskovBase {
    public void doStuff() {
        System.out.println("I'm a sub Liskov!");
    }
}

public class Context {
    private LiskovBase base;

    // the good stuff
    public void doLiskovyStuff() {
        base.doStuff();
    }

    public void setBase(LiskovBase base) { this.base = base }
}

कोड LSP करता है क्योंकि संदर्भ वास्तविक प्रकार को जाने बिना LiskovBase का उपयोग कर सकता है। आपको लगता है कि यह कोड OCP को भी अनुसरण करता है, लेकिन बारीकी से देखें, तो क्या वास्तव में क्लास बंद है ? क्या होगा यदि doStuffविधि केवल एक लाइन का प्रिंट आउट लेने से अधिक थी?

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

हम इस पर टेम्पलेट विधि पैटर्न लागू कर सकते हैं । टेम्पलेट विधि पैटर्न चौखटे में इतना आम है कि आप इसे जाने बिना उपयोग कर रहे होंगे (जैसे जावा स्विंग घटक, सी # फॉर्म और घटक, आदि)। यह है कि doStuffसंशोधन के लिए विधि को बंद करने का एक तरीका और सुनिश्चित करें कि यह जावा के finalकीवर्ड के साथ चिह्नित करके बंद रहता है । वह कीवर्ड किसी को भी क्लास को आगे करने से रोकता है (C # में आप sealedएक ही काम करने के लिए उपयोग कर सकते हैं )।

public class LiskovBase {
    // this is now a template method
    // the code that was duplicated
    public final void doStuff() {
        System.out.println(getStuffString());
    }

    // extension point, the code that "varies"
    // in LiskovBase and it's subclasses
    // called by the template method above
    // we expect it to be virtual and overridden
    public string getStuffString() {
        return "My name is Liskov";
    }
}

public class LiskovSub extends LiskovBase {
    // the extension overridden
    // the actual code that varied
    public string getStuffString() {
        return "I'm sub Liskov!";
    }
}

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

निष्कर्ष

मुझे आशा है कि यह सब OCP और LSP और उनके बीच के अंतर / समानता के बारे में कुछ प्रश्न स्पष्ट करता है। उन्हें समान रूप से खारिज करना आसान है लेकिन ऊपर दिए गए उदाहरणों से पता चलता है कि वे नहीं हैं।

ध्यान दें कि, उपरोक्त नमूना कोड से एकत्रित:

  • OCP वर्किंग कोड को लॉक करने के बारे में है, लेकिन फिर भी इसे किसी न किसी तरह के एक्सटेंशन पॉइंट्स के साथ खुला रखें।

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

  • एलएसपी उपयोगकर्ता को विभिन्न वस्तुओं को संभालने देने के बारे में है जो सुपरटेप को लागू किए बिना यह जांचते हैं कि वे वास्तविक प्रकार क्या हैं। यह स्वाभाविक रूप से बहुरूपता है।

    यह सिद्धांत टाइप-चेकिंग और टाइप-कनवर्ज़न करने का एक विकल्प प्रदान करता है, जो कि प्रकार की संख्या बढ़ने पर हाथ से बाहर निकल सकता है, और विज़िटर जैसे पुल-अप रिफैक्टरिंग या पैटर्न लागू करने के माध्यम से प्राप्त किया जा सकता है।


7
यह एक अच्छा स्पष्टीकरण है, क्योंकि यह ओसीपी को ओवरलिम्पलाइज़ नहीं करता है, इसका अर्थ यह है कि इसका अर्थ हमेशा विरासत द्वारा कार्यान्वयन है। यह कुछ लोगों के दिमाग में ओसीपी और एसआरपी में शामिल होने वाला ओवरसिम्प्लीफिकेशन है, जब वास्तव में वे दो पूरी तरह से अलग अवधारणा हो सकते हैं।
एरिक किंग

5
यह मैंने कभी देखा है सबसे अच्छा स्टैक एक्सचेंज उत्तरों में से एक है। काश मैं इसे 10 बार बढ़ा सकता। अच्छी तरह से किया, और उत्कृष्ट स्पष्टीकरण के लिए धन्यवाद।
बॉब हॉर्न

वहां, मैंने जावास्क्रिप्ट पर एक ब्लर्ब जोड़ा, जो क्लास-आधारित प्रोग्रामिंग भाषा नहीं है, लेकिन फिर भी एलएसपी का पालन कर सकता है और पाठ को संपादित कर सकता है, इसलिए यह उम्मीद है कि अधिक धाराप्रवाह पढ़ता है। ओह!
23

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

@ आलफा: यह एक अच्छा सवाल है। बेस क्लास हमेशा व्युत्पन्न वर्गों के साथ प्रतिस्थापित करने योग्य होती है, अन्यथा वंशानुक्रम काम नहीं करेगा। कंपाइलर (कम से कम जावा और सी # में) शिकायत करेगा कि क्या आप उस विस्तारित वर्ग से सदस्य (विधि या विशेषता / क्षेत्र) को छोड़ रहे हैं जिसे कार्यान्वित करने की आवश्यकता है। एलएसपी आपको उन विधियों को जोड़ने से रखने के लिए है जो केवल व्युत्पन्न वर्गों पर स्थानीय रूप से उपलब्ध हैं, क्योंकि इसके लिए उन व्युत्पन्न वर्गों के उपयोगकर्ता की आवश्यकता होती है। जैसे-जैसे कोड बढ़ता है, ऐसे तरीकों को बनाए रखना मुश्किल होगा।
23

15

यह एक ऐसी चीज है जो बहुत भ्रम पैदा करती है। मैं इन सिद्धांतों पर कुछ दार्शनिक रूप से विचार करना पसंद करता हूं, क्योंकि उनके लिए कई अलग-अलग उदाहरण हैं, और कभी-कभी ठोस उदाहरण वास्तव में उनके पूरे सार पर कब्जा नहीं करते हैं।

OCP ठीक करने के लिए क्या प्रयास करता है

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

उस के साथ समस्याएं हैं

  1. यह मौजूदा, काम कर रहे कोड के प्रवाह को बदलता है।
  2. यह हर मामले पर एक नई सशर्त शाखा के लिए बाध्य करता है। उदाहरण के लिए, मान लें कि आपके पास पुस्तकों की एक सूची है, और उनमें से कुछ बिक्री पर हैं, और आप उन सभी पर पुनरावृति करना चाहते हैं और उनकी कीमत प्रिंट करना चाहते हैं, जैसे कि यदि वे बिक्री पर हैं, तो मुद्रित मूल्य में स्ट्रिंग शामिल होगी " (बिक्री पर)"।

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

पहली, प्रक्रियात्मक समाधान के साथ समस्या, प्रत्येक पुस्तक के लिए एक अतिरिक्त क्षेत्र है, और कई मामलों में अतिरिक्त अनावश्यक जटिलता है। दूसरा समाधान केवल तर्क को बाध्य करता है जहां वास्तव में इसकी आवश्यकता होती है।

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

अब तक आपको मुख्य विचार प्राप्त करना चाहिए: अपने आप को ऐसी स्थिति में डालने की कोशिश करें जहां नया कोड बहुरूपी एक्सटेंशन के रूप में लागू किया जा सकता है, न कि प्रक्रियात्मक संशोधनों के रूप में।

लेकिन संदर्भ का विश्लेषण करने से कभी न डरें, और देखें कि क्या कमियां फायदे को कम करने के लिए होती हैं, क्योंकि यहां तक ​​कि एक सिद्धांत जैसे कि ओसीपी 20-लाइन प्रोग्राम से 20-वर्ग की गड़बड़ी कर सकता है, अगर सावधानी से इलाज नहीं किया जाता है

एलएसपी क्या तय करने की कोशिश करता है

हम सभी कोड का पुन: उपयोग करते हैं। एक बीमारी जो इस प्रकार है कि कई कार्यक्रम इसे पूरी तरह से नहीं समझते हैं, इस बिंदु पर जहां वे कोड की सामान्य लाइनों को नेत्रहीन रूप से फैक्टर कर रहे हैं, केवल अपठनीय जटिलताएं बनाने के लिए और कोड के कुछ लाइनों के अलावा, मॉड्यूल के बीच निरर्थक तंग-युग्मन, जहां तक ​​वैचारिक काम करने के लिए किया जाता है, वहां कुछ भी सामान्य नहीं है।

इसका सबसे बड़ा उदाहरण इंटरफ़ेस का पुन: उपयोग है । आपने शायद खुद इसे देखा है; एक वर्ग एक इंटरफ़ेस को लागू करता है, इसलिए नहीं कि इसका तार्किक कार्यान्वयन (या कंक्रीट बेस कक्षाओं के मामले में एक विस्तार), बल्कि इसलिए कि उस बिंदु पर घोषित करने के लिए जो तरीके होते हैं, वे सही हस्ताक्षर हैं जहां तक ​​यह चिंतित है।

लेकिन फिर आप एक समस्या का सामना करते हैं। यदि कक्षाएं केवल उन तरीकों के हस्ताक्षरों पर विचार करके इंटरफेस को लागू करती हैं, जो वे घोषित करते हैं, तो आप अपने आप को एक वैचारिक कार्यक्षमता से वर्गों के उदाहरणों को पारित करने में सक्षम पाते हैं, जो पूरी तरह से अलग कार्यक्षमता की मांग करते हैं, जो केवल इसी तरह के हस्ताक्षर पर निर्भर होने के लिए होता है।

वह भयानक नहीं है, लेकिन यह बहुत भ्रम का कारण बनता है, और हमारे पास इन जैसी गलतियों को करने से रोकने के लिए तकनीक है। हमें एपीआई + प्रोटोकॉल के रूप में इंटरफेस का इलाज करने की आवश्यकता है । एपीआई घोषणाओं में स्पष्ट है, और प्रोटोकॉल इंटरफ़ेस के मौजूदा उपयोगों में स्पष्ट है। यदि हमारे पास 2 वैचारिक प्रोटोकॉल हैं जो एक ही एपीआई को साझा करते हैं, तो उन्हें 2 अलग-अलग इंटरफेस के रूप में दर्शाया जाना चाहिए। अन्यथा हम DRY dogmatism में फंस जाते हैं और, विडंबना यह है कि केवल कोड बनाए रखने के लिए मुश्किल पैदा करते हैं।

अब आपको परिभाषा को पूरी तरह समझने में सक्षम होना चाहिए। एलएसपी कहता है: बेस क्लास से वारिस न करें और उन सब-क्लास में कार्यक्षमता को लागू करें जो, अन्य जगहों पर, जो बेस क्लास पर निर्भर हैं, को साथ नहीं मिलेगा।


1
मैंने केवल इस और स्पोइक के जवाबों को वोट करने में सक्षम होने के लिए साइन अप किया है - बहुत अच्छा काम।
डेविड क्यूप

7

मेरी समझ से:

OCP का कहना है: "यदि आप नई कार्यक्षमता जोड़ेंगे, तो एक नया वर्ग बना सकते हैं, जो किसी मौजूदा को विस्तार देने के बजाय उसे बदल देगा।"

एलएसपी कहता है: "यदि आप एक मौजूदा वर्ग का विस्तार करते हुए एक नया वर्ग बनाते हैं, तो सुनिश्चित करें कि यह अपने आधार के साथ पूरी तरह से विनिमेय है।"

इसलिए मुझे लगता है कि वे एक दूसरे के पूरक हैं लेकिन वे समान नहीं हैं।


4

हालांकि यह सच है कि OCP और LSP दोनों को संशोधन के साथ करना है, OCP के बारे में जिस तरह का संशोधन होता है, वह एक LSP की बात नहीं करता है।

OCP के संबंध में संशोधन करना किसी मौजूदा कक्षा में डेवलपर लेखन कोड की भौतिक क्रिया है ।

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

इसलिए यद्यपि वे OCP से दूर दिख सकते हैं! = LSP। वास्तव में मुझे लगता है कि वे केवल 2 ठोस सिद्धांत हो सकते हैं जिन्हें एक दूसरे के संदर्भ में नहीं समझा जा सकता है।


2

एलएसपी सरल शब्दों में कहता है कि फू के किसी भी उदाहरण को बार के किसी भी उदाहरण से बदला जा सकता है जो प्रोग्राम कार्यक्षमता के किसी भी नुकसान के बिना फू से प्राप्त होता है।

ये गलत है। एलएसपी बताता है कि क्लास बार व्यवहार का परिचय नहीं दे सकता है, यह उम्मीद नहीं है कि कोड फू का उपयोग करता है, जब बार फू से प्राप्त होता है। यह कार्यक्षमता की हानि के साथ कुछ नहीं करना है। आप कार्यक्षमता को हटा सकते हैं, लेकिन केवल जब फू का उपयोग कर कोड इस कार्यक्षमता पर निर्भर नहीं करता है।

लेकिन अंत में, यह आमतौर पर हासिल करना कठिन होता है, क्योंकि ज्यादातर समय, फू का उपयोग करने वाला कोड सभी के व्यवहार पर निर्भर करता है। इसलिए इसे हटाने से एलएसपी का उल्लंघन होता है। लेकिन इसे इस तरह सरल बनाना एलएसपी का ही एक हिस्सा है।


एक बहुत ही सामान्य मामला है जहां प्रतिस्थापित वस्तु साइड-इफेक्ट को हटा देती है : जैसे। एक डमी लकड़हारा, जो कुछ भी नहीं या परीक्षण में उपयोग की जाने वाली नकली वस्तु का उत्पादन करता है।
बेकार

0

उन वस्तुओं के बारे में जो उल्लंघन कर सकती हैं

अंतर को समझने के लिए आपको दोनों सिद्धांतों के विषयों को समझना चाहिए। यह कोड या स्थिति का कुछ अमूर्त हिस्सा नहीं है जो कुछ सिद्धांत का उल्लंघन कर सकता है या नहीं। यह हमेशा कुछ विशिष्ट घटक - फ़ंक्शन, वर्ग या एक मॉड्यूल है - जो OCP या LSP का उल्लंघन कर सकता है।

कौन एलएसपी का उल्लंघन कर सकता है

एक जाँच कर सकता है कि एलएसपी केवल तभी टूट जाता है जब कुछ अनुबंध के साथ एक इंटरफ़ेस होता है और उस इंटरफ़ेस का कार्यान्वयन होता है। यदि कार्यान्वयन इंटरफ़ेस के अनुरूप नहीं है, या आम तौर पर अनुबंध के लिए बोल रहा है, तो एलएसपी टूट गया है।

सरलतम उदाहरण:

class Container {
    // Should add the object to the container.
    void addObject(object) {
        internalArray.append(object);
    }

    int size() {
        return internalArray.size();
    }
}

class CustomContainer extends Container {
    @Override void addObject(object) {
        System.console.print("Skipping object! Ha-ha!");
    }
}

void fillWithRandomNumbers(Container container) {
    while (container.size() < 42) {
        container.addObject(Randomizer.getNumber())
    }
}

अनुबंध में स्पष्ट रूप से कहा गया है कि addObjectकंटेनर को अपना तर्क देना चाहिए। और CustomContainerस्पष्ट रूप से उस अनुबंध को तोड़ता है। इस प्रकार CustomContainer.addObjectफ़ंक्शन एलएसपी का उल्लंघन करता है। इस प्रकार CustomContainerवर्ग एलएसपी का उल्लंघन करता है। सबसे महत्वपूर्ण परिणाम यह है कि CustomContainerइसे पारित नहीं किया जा सकता है fillWithRandomNumbers()Containerके साथ प्रतिस्थापित नहीं किया जा सकता है CustomContainer

एक बहुत महत्वपूर्ण बिंदु को ध्यान में रखें। यह संपूर्ण कोड नहीं है जो एलएसपी को तोड़ता है, यह विशेष रूप से है CustomContainer.addObjectऔर आम तौर पर CustomContainerएलएसपी को तोड़ता है। जब आप कहते हैं कि एलएसपी का उल्लंघन होता है तो आपको हमेशा दो चीजें निर्दिष्ट करनी चाहिए:

  • एलएसपी का उल्लंघन करने वाली इकाई।
  • वह अनुबंध जो इकाई द्वारा तोड़ा जाता है।

बस। बस एक अनुबंध और इसके कार्यान्वयन। कोड में एक डाउनकास्ट एलएसपी उल्लंघन के बारे में कुछ नहीं कहता है।

जो OCP का उल्लंघन कर सकता है

कोई जाँच कर सकता है कि OCP का उल्लंघन केवल तभी होता है जब कोई सीमित डेटा सेट होता है और एक घटक जो उस डेटा सेट से मानों को संभालता है। यदि डेटा सेट की सीमाएं समय के साथ बदल सकती हैं और इसके लिए घटक के स्रोत कोड को बदलने की आवश्यकता होती है, तो घटक ओसीपी का उल्लंघन करता है।

जटिल लगता है। आइए एक सरल उदाहरण का प्रयास करें:

enum Platform {
    iOS,
    Android
}

class PlatformDescriber {
    String describe(Platform platform) {
        switch (platform) {
            case iOS: return "iPhone OS, v10.0.1";
            case Android: return "Android, v7.1";
        }
    }
}

डेटा सेट समर्थित प्लेटफ़ॉर्म का सेट है। PlatformDescriberवह घटक है जो उस डेटा सेट से मानों को संभालता है। एक नया प्लेटफ़ॉर्म जोड़ने के लिए स्रोत कोड अपडेट करना होगा PlatformDescriber। इस प्रकार PlatformDescriberवर्ग OCP का उल्लंघन करता है।

एक और उदाहरण:

class Shop {
    void sellItemToCustomer(item, customer) {
        // some buisiness logic here
        ...
        logger.logItemSold()
    }
}

class Logger {
    void logItemSold() {
        logger.logToStdErr("an item was sold")
        logger.logToRemote("an item was sold")
        logger.logToDatabase("an item was sold")
    }
}

"डेटा सेट" उन चैनलों का सेट है जहां एक लॉग प्रविष्टि को जोड़ा जाना चाहिए। Loggerवह घटक है जो सभी चैनलों में प्रविष्टियाँ जोड़ने के लिए जिम्मेदार है। लॉगिंग के दूसरे तरीके के लिए समर्थन जोड़ने के लिए स्रोत कोड को अपडेट करना होगा Logger। इस प्रकार Loggerवर्ग OCP का उल्लंघन करता है।

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

सीमाओं से अधिक जाना

अब मुश्किल हिस्सा है। उपरोक्त उदाहरणों की तुलना निम्न से करें:

enum GregorianWeekDay {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

String translateToRussian(GregorianWeekDay weekDay) {
    switch (weekDay) {
        case Monday: return "Понедельник";
        case Tuesday: return "Вторник";
        case Wednesday: return "Среда";
        case Thursday: return "Четверг";
        case Friday: return "Пятница";
        case Saturday: return "Суббота";
        case Sunday: return "Воскресенье";
    }
}

आपको लगता है कि translateToRussianOCP का उल्लंघन हो सकता है । लेकिन वास्तव में ऐसा नहीं है। GregorianWeekDayसटीक नामों के साथ 7 सप्ताह के दिनों की एक विशिष्ट सीमा है। और महत्वपूर्ण बात यह है कि ये सीमाएँ समय के साथ बदल नहीं सकती हैं। ग्रेगोरियन सप्ताह में हमेशा 7 दिन होंगे। हमेशा सोमवार, मंगलवार आदि होंगे। यह डेटा सेट शब्दार्थ से तय किया गया है। यह संभव नहीं है कि translateToRussianस्रोत कोड में संशोधनों की आवश्यकता होगी। इस प्रकार OCP का उल्लंघन नहीं किया जाता है।

अब यह स्पष्ट होना चाहिए कि थकावट वाला switchबयान हमेशा टूटे हुए OCP का संकेत नहीं है।

अंतर

अब फर्क महसूस करें:

  • एलएसपी का विषय "इंटरफ़ेस / अनुबंध का कार्यान्वयन" है। यदि कार्यान्वयन अनुबंध के अनुरूप नहीं है तो यह एलएसपी को तोड़ देता है। यह महत्वपूर्ण नहीं है कि यदि कार्यान्वयन समय के साथ बदल सकता है या नहीं, अगर यह एक्स्टेंसिबल है या नहीं।
  • OCP का विषय "आवश्यकताओं में परिवर्तन का जवाब देने का एक तरीका है"। यदि नए प्रकार के डेटा के लिए समर्थन के लिए उस डेटा को संभालने वाले घटक के स्रोत कोड को बदलने की आवश्यकता होती है, तो वह घटक OCP को तोड़ देता है। यह महत्वपूर्ण नहीं है कि घटक अपना अनुबंध तोड़ता है या नहीं।

ये स्थितियां पूरी तरह से रूढ़िवादी हैं।

उदाहरण

में @ Spoike का जवाब एक सिद्धांत का उल्लंघन है, लेकिन अन्य निम्नलिखित हिस्सा पूरी तरह से गलत है।

पहले उदाहरण में for-लूप का हिस्सा स्पष्ट रूप से ओसीपी का उल्लंघन कर रहा है क्योंकि यह संशोधन के बिना एक्स्टेंसिबल नहीं है। लेकिन एलएसपी उल्लंघन का कोई संकेत नहीं है। और यह भी स्पष्ट नहीं है कि Contextअनुबंध getPersons को छोड़कर कुछ भी वापस करने की अनुमति देता है Bossया नहीं Peon। यहां तक ​​कि एक अनुबंध को संभालने से जो किसी भी IPersonउपवर्ग को वापस करने की अनुमति देता है , कोई भी वर्ग नहीं है जो इस पोस्ट-कंडीशन को ओवरराइड करता है और इसका उल्लंघन करता है। अधिक से अधिक, अगर getPersons किसी तीसरे वर्ग का उदाहरण लौटाएगा, -लूप forबिना किसी विफलता के अपना काम करेगा। लेकिन उस तथ्य का एलएसपी से कोई लेना-देना नहीं है।

आगे। दूसरे उदाहरण में न तो एलएसपी, न ही ओसीपी का उल्लंघन किया जाता है। फिर, Contextभाग का एलएसपी से कोई लेना-देना नहीं है - कोई परिभाषित अनुबंध, कोई उपवर्ग, कोई ब्रेकिंग ओवरराइड नहीं। यह नहीं है कि Contextकिसे एलएसपी का पालन करना चाहिए, इसे LiskovSubअपने आधार के अनुबंध को नहीं तोड़ना चाहिए। OCP के बारे में, क्या वास्तव में वर्ग बंद है? - हाँ यही है। इसे बढ़ाने के लिए किसी संशोधन की आवश्यकता नहीं है। स्पष्ट रूप से विस्तार बिंदु का नाम बताता है कि आप कुछ भी चाहते हैं, कोई सीमा नहीं । उदाहरण वास्तविक जीवन में बहुत उपयोगी नहीं है, लेकिन यह स्पष्ट रूप से ओसीपी का उल्लंघन नहीं करता है।

आइए OCP या LSP के सही उल्लंघन के साथ कुछ सही उदाहरण बनाने की कोशिश करें।

OCP का पालन करें लेकिन LSP का नहीं

interface Platform {
    String name();
    String version();
}

class iOS implements Platform {
    @Override String name() { return "iOS"; }
    @Override String version() { return "10.0.1"; }
}

interface PlatformSerializer {
    String toJson(Platform platform);
}

class HumanReadablePlatformSerializer implements PlatformSerializer {
    String toJson(Platform platform) {
        return platform.name() + ", v" + platform.version();
    }
}

HumanReadablePlatformSerializerजब कोई नया प्लेटफॉर्म जोड़ा जाता है , तो यहां किसी भी संशोधन की आवश्यकता नहीं होती है। इस प्रकार यह OCP का अनुसरण करता है।

लेकिन अनुबंध की आवश्यकता है कि toJsonएक ठीक से स्वरूपित JSON वापस करना चाहिए। वर्ग ऐसा नहीं करता है। इस वजह से यह एक घटक को पारित नहीं किया जा सकता PlatformSerializerहै जो नेटवर्क अनुरोध के शरीर को प्रारूपित करता है। इस प्रकार HumanReadablePlatformSerializerएलएसपी का उल्लंघन होता है।

एलएसपी का पालन करें लेकिन ओसीपी नहीं

पिछले उदाहरण के लिए कुछ संशोधन:

class Android implements Platform {
    @Override String name() { return "Android"; }
    @Override String version() { return "7.1"; }
}
class HumanReadablePlatformSerializer implements PlatformSerializer {
    String toJson(Platform platform) {
        return "{ "
                + "\"name\": \"" + platform.name() + "\","
                + "\"version\": \"" + platform.version() + "\","
                + "\"most-popular\": " + isMostPopular(platform) + ","
                + "}"
    }

    boolean isMostPopular(Platform platform) {
        return (platform instanceof Android)
    }
}

धारावाहिक सही ढंग से JSON स्ट्रिंग स्वरूपित करता है। तो, यहाँ कोई एलएसपी उल्लंघन नहीं।

लेकिन एक आवश्यकता यह है कि यदि प्लेटफ़ॉर्म सबसे अधिक उपयोग किया जाता है तो JSON में संबंधित संकेत होना चाहिए। इस उदाहरण में OCP HumanReadablePlatformSerializer.isMostPopularफ़ंक्शन द्वारा उल्लंघन किया जाता है क्योंकि किसी दिन iOS सबसे लोकप्रिय प्लेटफॉर्म बन जाता है। औपचारिक रूप से इसका मतलब है कि अधिकांश उपयोग किए गए प्लेटफार्मों के सेट को अभी के लिए "एंड्रॉइड" के रूप में परिभाषित किया गया है, और isMostPopularअपर्याप्त रूप से उस डेटा सेट को संभालता है। डेटा सेट शब्दार्थ रूप से तय नहीं है और समय के साथ स्वतंत्र रूप से बदल सकता है। HumanReadablePlatformSerializerपरिवर्तन के मामले में स्रोत कोड को अपडेट किया जाना आवश्यक है।

आप इस उदाहरण में सिंगल रिस्पॉन्सिबिलिटी का उल्लंघन भी देख सकते हैं। मैंने जानबूझकर ऐसा किया है कि एक ही विषय इकाई पर दोनों सिद्धांतों को प्रदर्शित करने में सक्षम हो। SRP को ठीक करने के लिए आप isMostPopularफ़ंक्शन को कुछ बाहरी में निकाल सकते हैं Helperऔर इसमें एक पैरामीटर जोड़ सकते हैं PlatformSerializer.toJson। लेकिन यह एक और कहानी है।


0

एलएसपी और ओसीपी समान नहीं हैं।

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

ओसीपी प्रोग्राम कोड में बदलाव की शुद्धता के बारे में बात करता है , डेल्टा एक स्रोत संस्करण से दूसरे तक। व्यवहार को संशोधित नहीं किया जाना चाहिए। इसे केवल बढ़ाया जाना चाहिए। क्लासिक उदाहरण फ़ील्ड जोड़ है। सभी मौजूदा फ़ील्ड पहले की तरह काम कर रहे हैं। नया फ़ील्ड केवल कार्यक्षमता जोड़ता है। हालाँकि किसी क्षेत्र को हटाना, आमतौर पर OCP का उल्लंघन है। यहां आप यह देखने के लिए प्रोग्राम संस्करण डेल्टा सत्यापित कर रहे हैं कि क्या यह OCP से मिलता है।

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

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


-1

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


यह बहुत रचनात्मक होगा यदि लोग किसी भी जवाब को वोट करते समय टिप्पणी भी करते हैं। आखिरकार हम सभी ज्ञान साझा करने के लिए यहां हैं, और बिना उचित कारण के केवल निर्णय पारित करना किसी भी उद्देश्य की पूर्ति नहीं करने वाला है।
AKS

इस कुछ भी पर्याप्त पेशकश करने के लिए अंक बनाया और में पहले 6 जवाब समझाया नहीं लगता है
कुटकी

1
ऐसा लगता है जैसे आप सिर्फ एक सिद्धांत की व्याख्या कर रहे हैं, एल एक मुझे लगता है। किस लिए यह ठीक है, लेकिन दो अलग-अलग सिद्धांतों की तुलना / विपरीत के लिए पूछा गया प्रश्न। शायद इसीलिए किसी ने इसे नीचा दिखाया।
StarWeaver
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.