मैंने सुना है कि लिसकोव प्रतिस्थापन सिद्धांत (एलएसपी) वस्तु उन्मुख डिजाइन का एक मूल सिद्धांत है। यह क्या है और इसके उपयोग के कुछ उदाहरण क्या हैं?
मैंने सुना है कि लिसकोव प्रतिस्थापन सिद्धांत (एलएसपी) वस्तु उन्मुख डिजाइन का एक मूल सिद्धांत है। यह क्या है और इसके उपयोग के कुछ उदाहरण क्या हैं?
जवाबों:
एक बढ़िया उदाहरण एलएसपी (उदाहरण में मैंने हाल ही में सुना है पोडकास्ट में अंकल बॉब द्वारा दिया गया) था कि कैसे कभी-कभी कुछ ऐसा होता है जो प्राकृतिक भाषा में सही लगता है, कोड में काफी काम नहीं करता है।
गणित में, a Square
एक है Rectangle
। वास्तव में यह एक आयत की विशेषज्ञता है। "एक है" आपको यह विरासत के साथ मॉडल करना चाहता है। हालाँकि, यदि कोड में आप से Square
व्युत्पन्न किया गया है Rectangle
, तो एक Square
उम्मीद के मुताबिक होना चाहिए कहीं भी आप एक उम्मीद है Rectangle
। यह कुछ अजीब व्यवहार के लिए बनाता है।
कल्पना कीजिए कि आपके पास SetWidth
और SetHeight
आपके Rectangle
आधार वर्ग के तरीके थे ; यह पूरी तरह से तर्कसंगत लगता है। लेकिन अगर आपके Rectangle
संदर्भ एक की ओर इशारा किया Square
, तो SetWidth
और SetHeight
कोई मतलब नहीं है क्योंकि एक की स्थापना अन्य इससे मिलते हुए बदल जाएगा। इस मामले में के Square
साथ Liskov प्रतिस्थापन परीक्षण विफल रहता है Rectangle
और Square
से विरासत में प्राप्त करने का अमूर्त Rectangle
एक बुरा है।
Y'all को अन्य अमूल्य SOLID सिद्धांत प्रेरक पोस्टर की जाँच करनी चाहिए ।
Square.setWidth(int width)
इस तरह से लागू किया गया तो समस्या क्यों होगी this.width = width; this.height = width;
:? इस मामले में यह गारंटी है कि चौड़ाई ऊंचाई के बराबर है।
लिस्कोव प्रतिस्थापन सिद्धांत (एलएसपी, LSP) ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग में एक अवधारणा है जो बताता है:
आधार वर्गों के लिए संकेत या संदर्भ का उपयोग करने वाले कार्य यह जानने के बिना व्युत्पन्न वर्गों की वस्तुओं का उपयोग करने में सक्षम होना चाहिए।
अपने दिल में एलएसपी इंटरफेस और कॉन्ट्रैक्ट्स के साथ-साथ यह भी तय करता है कि किसी वर्ग का विस्तार कब करना है या किसी अन्य रणनीति का उपयोग करें जैसे कि अपने लक्ष्य को प्राप्त करने के लिए रचना।
इस बिंदु को स्पष्ट करने के लिए मैंने सबसे प्रभावी तरीका हेड फ़र्स्ट ओओएएंडडी में देखा था । वे एक परिदृश्य प्रस्तुत करते हैं जहां आप रणनीति गेम के लिए एक रूपरेखा बनाने के लिए एक परियोजना पर एक डेवलपर हैं।
वे एक वर्ग प्रस्तुत करते हैं जो एक बोर्ड का प्रतिनिधित्व करता है जो इस तरह दिखता है:
सभी विधियां X और Y को दो-आयामी सरणी में टाइल की स्थिति का पता लगाने के लिए मापदंडों के रूप में समन्वयित करती हैं Tiles
। यह गेम डेवलपर को गेम के दौरान बोर्ड में इकाइयों का प्रबंधन करने की अनुमति देगा।
पुस्तक यह कहने के लिए आवश्यकताओं को बदलने के लिए जाती है कि गेम फ्रेम काम को उन खेलों को समायोजित करने के लिए 3 डी गेम बोर्डों का भी समर्थन करना चाहिए जिनके पास उड़ान है। तो एक ThreeDBoard
वर्ग पेश किया जाता है जो विस्तारित होता है Board
।
पहली नज़र में यह एक अच्छा निर्णय लगता है। Board
दोनों Height
और Width
गुण ThreeDBoard
प्रदान करता है और Z अक्ष प्रदान करता है।
यह तब टूटता है जब आप विरासत में मिले अन्य सभी सदस्यों को देखते हैं Board
। के लिए तरीके AddUnit
, GetTile
, GetUnits
और इतने पर, सब दोनों एक्स और वाई मापदंडों में ले Board
वर्ग लेकिन ThreeDBoard
साथ ही एक जेड पैरामीटर की जरूरत है।
तो आपको एक Z पैरामीटर के साथ उन तरीकों को फिर से लागू करना होगा। Z पैरामीटर का Board
वर्ग के लिए कोई संदर्भ नहीं है और कक्षा से विरासत में मिली विधियां Board
अपना अर्थ खो देती हैं। कोड का एक इकाई ThreeDBoard
वर्ग को अपने आधार वर्ग के रूप में उपयोग करने का प्रयास कर रहा है Board
।
शायद हमें एक और दृष्टिकोण खोजना चाहिए। विस्तार करने के बजाय Board
, वस्तुओं से ThreeDBoard
बना होना चाहिए Board
। Board
जेड अक्ष की प्रति इकाई एक वस्तु।
यह हमें एनकैप्सुलेशन और पुनः उपयोग जैसे अच्छे ऑब्जेक्ट उन्मुख सिद्धांतों का उपयोग करने की अनुमति देता है और एलएसपी का उल्लंघन नहीं करता है।
सबस्टिबिलिटी, ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग में एक सिद्धांत है, जिसमें कहा गया है कि, कंप्यूटर प्रोग्राम में, यदि S, T का उपप्रकार है, तो टाइप T की वस्तुओं को S के ऑब्जेक्ट से बदला जा सकता है।
आइए जावा में एक सरल उदाहरण देते हैं:
public class Bird{
public void fly(){}
}
public class Duck extends Bird{}
बतख उड़ सकता है क्योंकि यह एक पक्षी है, लेकिन इसके बारे में क्या:
public class Ostrich extends Bird{}
शुतुरमुर्ग एक पक्षी है, लेकिन यह उड़ नहीं सकता है, शुतुरमुर्ग वर्ग पक्षी का एक उपप्रकार है, लेकिन यह मक्खी विधि का उपयोग नहीं कर सकता है, इसका मतलब है कि हम एलएसपी सिद्धांत को तोड़ रहे हैं।
public class Bird{
}
public class FlyingBirds extends Bird{
public void fly(){}
}
public class Duck extends FlyingBirds{}
public class Ostrich extends Bird{}
Bird bird
। फ्लाइंग का उपयोग करने के लिए आपको फ्लाइंगबर्ड्स का ऑब्जेक्ट डालना होगा, जो अच्छा नहीं है?
Bird bird
, तो इसका मतलब है कि वह उपयोग नहीं कर सकता है fly()
। बस। पास होने से Duck
यह तथ्य नहीं बदलता है। यदि क्लाइंट के पास है FlyingBirds bird
, तो भले ही वह पास हो जाए लेकिन उसे Duck
हमेशा उसी तरह से काम करना चाहिए।
एलएसपी चिंताओं से चिंतित है।
क्लासिक उदाहरण निम्नलिखित छद्म कोड घोषणा (छोड़ा गया कार्यान्वयन) द्वारा दिया गया है:
class Rectangle {
int getHeight()
void setHeight(int value)
int getWidth()
void setWidth(int value)
}
class Square : Rectangle { }
अब हमें एक समस्या है, हालांकि इंटरफ़ेस मेल खाता है। इसका कारण यह है कि हमने वर्गों और आयतों की गणितीय परिभाषा से उपजी आक्रमणकारियों का उल्लंघन किया है। जिस तरह से काम करता है और बसता है Rectangle
, उसे निम्न आवेग को पूरा करना चाहिए:
void invariant(Rectangle r) {
r.setHeight(200)
r.setWidth(100)
assert(r.getHeight() == 200 and r.getWidth() == 100)
}
हालाँकि, इस अपरिवर्तनीय का सही कार्यान्वयन द्वारा उल्लंघन किया जाना चाहिए Square
, इसलिए यह एक वैध विकल्प नहीं है Rectangle
।
रॉबर्ट मार्टिन के पास Liskov प्रतिस्थापन सिद्धांत पर एक उत्कृष्ट पेपर है । यह सूक्ष्म और नहीं-तो-सूक्ष्म तरीकों पर चर्चा करता है जिसमें सिद्धांत का उल्लंघन हो सकता है।
कागज के कुछ प्रासंगिक हिस्से (ध्यान दें कि दूसरा उदाहरण बहुत घनीभूत है):
एलएसपी के उल्लंघन का एक सरल उदाहरण
इस सिद्धांत का सबसे भयावह उल्लंघन एक वस्तु के प्रकार के आधार पर एक फ़ंक्शन का चयन करने के लिए C ++ रन-टाइम टाइप सूचना (RTTI) का उपयोग है। अर्थात:
void DrawShape(const Shape& s) { if (typeid(s) == typeid(Square)) DrawSquare(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) DrawCircle(static_cast<Circle&>(s)); }
स्पष्ट रूप से
DrawShape
फ़ंक्शन बुरी तरह से बनता है। इसेShape
कक्षा के हर संभव व्युत्पन्न के बारे में पता होना चाहिए , और जब भी नया व्युत्पन्न बनता है, तो इसे बदलना होगाShape
। वास्तव में, कई इस फ़ंक्शन की संरचना को ऑब्जेक्ट ओरिएंटेड डिज़ाइन के लिए एंथम के रूप में देखते हैं।स्क्वायर और आयत, एक अधिक सूक्ष्म उल्लंघन।
हालांकि, एलएसपी के उल्लंघन के तरीके अन्य, कहीं अधिक सूक्ष्म हैं। एक आवेदन पर विचार करें जो
Rectangle
नीचे वर्णित वर्ग का उपयोग करता है :class Rectangle { public: void SetWidth(double w) {itsWidth=w;} void SetHeight(double h) {itsHeight=w;} double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; };
[...] कल्पना कीजिए कि एक दिन उपयोगकर्ता आयतों के अलावा वर्गों में हेरफेर करने की क्षमता की मांग करते हैं। [...]
स्पष्ट रूप से, एक वर्ग सभी सामान्य इरादों और उद्देश्यों के लिए एक आयत है। चूंकि ISA संबंध रखता है, इसलिए
Square
वर्ग को व्युत्पन्न किया जाना तर्कसंगत हैRectangle
। [...]
Square
कार्योंSetWidth
औरSetHeight
कार्यों को विरासत में मिलेगा । ये कार्य एक के लिए पूरी तरह से अनुचित हैंSquare
, क्योंकि एक वर्ग की चौड़ाई और ऊंचाई समान हैं। यह एक महत्वपूर्ण सुराग होना चाहिए कि डिजाइन के साथ कोई समस्या है। हालांकि, समस्या को दूर करने का एक तरीका है। हम ओवरराइड कर सकते थेSetWidth
औरSetHeight
[...]लेकिन निम्नलिखित कार्य पर विचार करें:
void f(Rectangle& r) { r.SetWidth(32); // calls Rectangle::SetWidth }
यदि हम
Square
इस फ़ंक्शन में किसी ऑब्जेक्ट का संदर्भ देते हैं , तोSquare
ऑब्जेक्ट दूषित हो जाएगा क्योंकि ऊँचाई को परिवर्तित नहीं किया जाएगा। यह एलएसपी का स्पष्ट उल्लंघन है। फ़ंक्शन अपने तर्कों के व्युत्पन्न के लिए काम नहीं करता है।[...]
Now the rule for the preconditions and postconditions for derivatives, as stated by Meyer is: ...when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
यदि एक बच्चे की कक्षा की पूर्व-स्थिति माता-पिता की पूर्व-स्थिति से अधिक मजबूत है, तो आप पूर्व-स्थिति का उल्लंघन किए बिना एक बच्चे को माता-पिता के लिए स्थानापन्न नहीं कर सकते। इसलिए एल.एस.पी.
एलएसपी आवश्यक है जहां कुछ कोड सोचते हैं कि यह एक प्रकार के तरीकों को बुला रहा है T
, और अनजाने में एक प्रकार के तरीकों को कॉल कर सकता है S
, जहां S extends T
(अर्थात S
, इनहेरिट्स से व्युत्पन्न होता है, या इसका एक उपप्रकार, सुपरटाइप है T
)।
उदाहरण के लिए, यह होता है जहां एक प्रकार के इनपुट पैरामीटर के साथ एक फ़ंक्शन T
, प्रकार के तर्क मान के साथ (यानी आह्वान किया जाता है) S
। या, जहां प्रकार का एक पहचानकर्ता, प्रकार का T
मान असाइन किया गया है S
।
val id : T = new S() // id thinks it's a T, but is a S
एलएसपी को प्रकार के तरीकों T
(जैसे ) के लिए अपेक्षाओं (यानी अपरिवर्तनीयों) की आवश्यकता होती है Rectangle
, जब प्रकार के तरीकों S
(जैसे Square
) के बजाय इसका उल्लंघन नहीं किया जाता है।
val rect : Rectangle = new Square(5) // thinks it's a Rectangle, but is a Square
val rect2 : Rectangle = rect.setWidth(10) // height is 10, LSP violation
यहां तक कि अपरिवर्तनीय क्षेत्रों के साथ एक प्रकार अभी भी अपरिवर्तनीय है, जैसे कि अपरिवर्तनीय आयत बसने वाले आयामों को स्वतंत्र रूप से संशोधित करने की उम्मीद करते हैं, लेकिन अपरिवर्तनीय वर्ग इस अपेक्षा का उल्लंघन करते हैं।
class Rectangle( val width : Int, val height : Int )
{
def setWidth( w : Int ) = new Rectangle(w, height)
def setHeight( h : Int ) = new Rectangle(width, h)
}
class Square( val side : Int ) extends Rectangle(side, side)
{
override def setWidth( s : Int ) = new Square(s)
override def setHeight( s : Int ) = new Square(s)
}
एलएसपी के लिए आवश्यक है कि उपप्रकार के प्रत्येक तरीके में S
कंट्रावेरेंट इनपुट पैरामीटर (एस) और एक सहसंयोजक आउटपुट होना चाहिए।
कंट्रावेरिएंट का अर्थ है कि विचरण विरासत की दिशा के विपरीत है, अर्थात Si
, उपप्रकार की प्रत्येक विधि के प्रत्येक इनपुट पैरामीटर का प्रकार S
, समान या सुपरपेप्ट के Ti
संबंधित विधि के संबंधित इनपुट पैरामीटर के प्रकार का एक सुपरस्क्रिप्ट होना चाहिए। T
।
कोवरिएनस का अर्थ है कि विचरण विरासत की एक ही दिशा में है, अर्थात So
, उपप्रकार के प्रत्येक विधि के आउटपुट का प्रकार S
, समान या उपप्रकार के To
संबंधित विधि के संबंधित आउटपुट के प्रकार का एक उपप्रकार होना चाहिए T
।
ऐसा इसलिए है क्योंकि यदि कॉलर सोचता है कि उसके पास एक प्रकार है T
, तो सोचता है कि यह एक विधि कह रहा है T
, तो यह प्रकार के तर्क (ओं) की आपूर्ति Ti
करता है और प्रकार को आउटपुट प्रदान करता है To
। जब यह वास्तव में की इसी विधि को बुला रहा है S
, तो प्रत्येक Ti
इनपुट तर्क को एक Si
इनपुट पैरामीटर सौंपा जाता है , और So
आउटपुट प्रकार को सौंपा जाता है To
। इस प्रकार, यदि Si
contravariant को wrt नहीं किया गया था Ti
, तो एक उपप्रकार- Xi
जो कि उप-प्रकार नहीं होगा, Si
को सौंपा जाएगा Ti
।
इसके अतिरिक्त, भाषाओं के लिए (जैसे स्काला या सीलोन), जिसमें टाइप पॉलीमॉर्फिज़्म पैरामीटर (यानी जेनरिक) पर परिभाषा-साइट विचरण एनोटेशन होते हैं, प्रत्येक प्रकार के पैरामीटर के लिए विचरण एनोटेशन के सह- या इंजेक्शन- दिशा विपरीत या समान दिशा T
होनी चाहिए क्रमशः हर इनपुट पैरामीटर या आउटपुट (हर विधि का ) जिसमें टाइप पैरामीटर होता है।T
इसके अतिरिक्त, प्रत्येक इनपुट पैरामीटर या आउटपुट के लिए जिसमें फ़ंक्शन प्रकार होता है, आवश्यक विचरण दिशा उलट होती है। यह नियम पुनरावर्ती रूप से लागू किया जाता है।
सबटाइपिंग उपयुक्त है जहां आक्रमणकारियों को एन्यूमरेट किया जा सकता है।
आक्रमणकारियों को कैसे मॉडल किया जाए, इस पर बहुत शोध चल रहा है, ताकि वे कंपाइलर द्वारा लागू किए जाएं।
टाइपिस्टेट (देखें पेज 3) टाइप करने के लिए राज्य आक्रमणकारियों को ऑर्थोगोनल घोषित करता है और लागू करता है। वैकल्पिक रूप से, आक्रमणकारियों को प्रकारों के लिए अभिसरण द्वारा लागू किया जा सकता है । उदाहरण के लिए, यह पता लगाने के लिए कि कोई फ़ाइल बंद करने से पहले खुली है, तब File.open () एक OpenFile प्रकार वापस कर सकता है, जिसमें एक बंद () विधि शामिल है जो फ़ाइल में उपलब्ध नहीं है। एक टिक टीएसी को पैर की अंगुली एपीआई टाइपिंग रोजगार संकलन समय पर अपरिवर्तनशीलताओं लागू करने के लिए का एक और उदाहरण हो सकता है। टाइप सिस्टम ट्यूरिंग-पूर्ण भी हो सकता है, उदाहरण के लिए स्काला । भरोसेमंद रूप से टाइप की गई भाषाएं और प्रमेय उच्च-क्रम टाइपिंग के मॉडल को औपचारिक रूप से सिद्ध करते हैं।
विस्तार पर अमूर्तता के लिए शब्दार्थ की आवश्यकता के कारण , मैं उम्मीद करता हूं कि मॉडल इनवेरिएंट्स के लिए टाइपिंग को नियोजित करता है, अर्थात एकीकृत उच्च-क्रमिक निरूपण शब्दार्थ, टाइपेस्ट से बेहतर है। Means एक्सटेंशन ’का अर्थ है अनकवर्ड, मॉड्यूलर डेवलपमेंट की अनबाउंड, पर्मेटेड रचना। क्योंकि यह मुझे एकरूपता का विरोधी लगता है और इस तरह से डिग्री-ऑफ-फ्रीडम, साझा किए गए शब्दार्थ को व्यक्त करने के लिए दो परस्पर-निर्भर मॉडल (जैसे प्रकार और टाइपस्टेट) है, जो एक दूसरे के साथ एकीकृत नहीं किया जा सकता है। । उदाहरण के लिए, एक्सप्रेशन प्रॉब्लम -एक्सटेंस एक्सटेंशन को सबटाइपिंग, फंक्शन ओवरलोडिंग और पैरामीट्रिक टाइपिंग डोमेन में एकीकृत किया गया था।
मेरी सैद्धांतिक स्थिति यह है कि ज्ञान के अस्तित्व के लिए (देखें अनुभाग "केंद्रीयकरण अंधा और अयोग्य है"), एक सामान्य मॉडल कभी नहीं होगा जो ट्यूरिंग-पूर्ण कंप्यूटर भाषा में सभी संभावित आक्रमणकारियों के 100% कवरेज को लागू कर सकता है। ज्ञान के अस्तित्व के लिए, अप्रत्याशित संभावनाएं बहुत मौजूद हैं, अर्थात विकार और एन्ट्रापी हमेशा बढ़ते रहना चाहिए। यह एन्ट्रापिक बल है। एक संभावित विस्तार के सभी संभावित संगणनाओं को साबित करने के लिए, प्राथमिकताओं को सभी संभव विस्तार की गणना करना है।
यही कारण है कि हाल्टिंग प्रमेय मौजूद है, अर्थात यह अकल्पनीय है कि क्या ट्यूरिंग-पूर्ण प्रोग्रामिंग भाषा में हर संभव कार्यक्रम समाप्त हो जाता है। यह साबित किया जा सकता है कि कुछ विशिष्ट कार्यक्रम समाप्त हो जाते हैं (एक जिसे सभी संभावनाओं को परिभाषित और गणना किया गया है)। लेकिन यह साबित करना असंभव है कि उस कार्यक्रम का सभी संभव विस्तार समाप्त हो जाता है, जब तक कि उस कार्यक्रम के विस्तार की संभावनाएं पूर्ण रूप से ट्यूरिंग नहीं होती हैं (जैसे आश्रित-टाइपिंग के माध्यम से)। चूंकि ट्यूरिंग-पूर्णता के लिए मूलभूत आवश्यकता अबाधित पुनरावृत्ति है , यह समझना सहज है कि गोडेल की अपूर्णता प्रमेयों और रसेल के विरोधाभास के विस्तार पर कैसे लागू होती है।
इन प्रमेयों की एक व्याख्या उन्हें एन्ट्रापिक बल की सामान्यीकृत वैचारिक समझ में शामिल करती है:
यह निर्धारित करने के लिए एक जांच सूची है कि आप लिस्कोव का उल्लंघन कर रहे हैं या नहीं।
सूची देखें:
इतिहास की बाधा : जब किसी विधि को ओवरराइड किया जाता है तो आपको बेस क्लास में एक अपरिवर्तनीय संपत्ति को संशोधित करने की अनुमति नहीं होती है। इन कोडों पर एक नज़र डालें और आप देख सकते हैं कि नाम को परिभाषित नहीं किया जा सकता है (निजी सेट)
public class SuperType
{
public string Name { get; private set; }
public SuperType(string name, int age)
{
Name = name;
Age = age;
}
}
public class SubType : SuperType
{
public void ChangeName(string newName)
{
var propertyType = base.GetType().GetProperty("Name").SetValue(this, newName);
}
}
2 अन्य आइटम हैं: विधि तर्कों का कंट्रोवर्सी और रिटर्न प्रकार के कोवरियन । लेकिन यह C # में संभव नहीं है (मैं C # डेवलपर हूं) इसलिए मुझे उनकी परवाह नहीं है।
संदर्भ:
मैं हर जवाब में आयतों और चौकों को देखता हूं, और एलएसपी का उल्लंघन कैसे करता हूं।
मैं यह दिखाना चाहता हूं कि एक वास्तविक दुनिया के उदाहरण के साथ एलएसपी कैसे अनुरूप हो सकता है:
<?php
interface Database
{
public function selectQuery(string $sql): array;
}
class SQLiteDatabase implements Database
{
public function selectQuery(string $sql): array
{
// sqlite specific code
return $result;
}
}
class MySQLDatabase implements Database
{
public function selectQuery(string $sql): array
{
// mysql specific code
return $result;
}
}
यह डिज़ाइन एलएसपी के अनुरूप है क्योंकि व्यवहार उस कार्यान्वयन के बिना अपरिवर्तित रहता है जिसे हम उपयोग करने के लिए चुनते हैं।
और हाँ, आप इस विन्यास में एलएसपी का उल्लंघन कर सकते हैं जैसे कि एक साधारण परिवर्तन करना:
<?php
interface Database
{
public function selectQuery(string $sql): array;
}
class SQLiteDatabase implements Database
{
public function selectQuery(string $sql): array
{
// sqlite specific code
return $result;
}
}
class MySQLDatabase implements Database
{
public function selectQuery(string $sql): array
{
// mysql specific code
return ['result' => $result]; // This violates LSP !
}
}
अब उप-प्रकारों का उपयोग उसी तरह से नहीं किया जा सकता है क्योंकि वे अब और समान परिणाम नहीं देते हैं।
Database::selectQuery
समर्थन सिर्फ एसक्यूएल के सबसेट द्वारा समर्थित करने के लिए सभी डीबी इंजन। यह शायद ही व्यावहारिक है ... यह कहा, उदाहरण अभी भी यहाँ इस्तेमाल किया अन्य लोगों की तुलना में समझ आसान है।
एलएसपी क्लैस के अनुबंध के बारे में एक नियम है: यदि एक बेस क्लास एक अनुबंध को संतुष्ट करता है, तो एलएसपी द्वारा व्युत्पन्न वर्गों को भी उस अनुबंध को पूरा करना होगा।
स्यूडो-पायथन में
class Base:
def Foo(self, arg):
# *... do stuff*
class Derived(Base):
def Foo(self, arg):
# *... do stuff*
एलएसपी को संतुष्ट करता है यदि हर बार जब आप फू को एक व्युत्पन्न वस्तु पर कहते हैं, तो यह ठीक उसी तरह से परिणाम देता है जैसे फू को बेस ऑब्जेक्ट पर कॉल करना, जब तक कि आरजी एक ही है।
2 + "2"
)। शायद आप "स्टेटिकली टाइप्ड" के साथ "दृढ़ता से टाइप" भ्रमित करते हैं?
लंबी कहानी छोटी, चलो आयतें आयतों और वर्गों को छोड़ दें, व्यावहारिक उदाहरण जब एक मूल वर्ग का विस्तार करते हैं, तो आपको सटीक माता-पिता एपीआई या आईटी को बाहर निकालने के लिए या तो सटीक करना होगा।
चलो कहते हैं कि तुम एक है आधार ItemsRepository।
class ItemsRepository
{
/**
* @return int Returns number of deleted rows
*/
public function delete()
{
// perform a delete query
$numberOfDeletedRows = 10;
return $numberOfDeletedRows;
}
}
और एक उप वर्ग इसका विस्तार कर रहा है:
class BadlyExtendedItemsRepository extends ItemsRepository
{
/**
* @return void Was suppose to return an INT like parent, but did not, breaks LSP
*/
public function delete()
{
// perform a delete query
$numberOfDeletedRows = 10;
// we broke the behaviour of the parent class
return;
}
}
तब आपके पास आधार आइटम्स के साथ काम करने वाला एक क्लाइंट हो सकता है ।
/**
* Class ItemsService is a client for public ItemsRepository "API" (the public delete method).
*
* Technically, I am able to pass into a constructor a sub-class of the ItemsRepository
* but if the sub-class won't abide the base class API, the client will get broken.
*/
class ItemsService
{
/**
* @var ItemsRepository
*/
private $itemsRepository;
/**
* @param ItemsRepository $itemsRepository
*/
public function __construct(ItemsRepository $itemsRepository)
{
$this->itemsRepository = $itemsRepository;
}
/**
* !!! Notice how this is suppose to return an int. My clients expect it based on the
* ItemsRepository API in the constructor !!!
*
* @return int
*/
public function delete()
{
return $this->itemsRepository->delete();
}
}
LSP जब टूट गया है प्रतिस्थापन माता-पिता एक साथ वर्ग उप वर्ग टूटता एपीआई अनुबंध ।
class ItemsController
{
/**
* Valid delete action when using the base class.
*/
public function validDeleteAction()
{
$itemsService = new ItemsService(new ItemsRepository());
$numberOfDeletedItems = $itemsService->delete();
// $numberOfDeletedItems is an INT :)
}
/**
* Invalid delete action when using a subclass.
*/
public function brokenDeleteAction()
{
$itemsService = new ItemsService(new BadlyExtendedItemsRepository());
$numberOfDeletedItems = $itemsService->delete();
// $numberOfDeletedItems is a NULL :(
}
}
आप मेरे पाठ्यक्रम में बनाए रखने योग्य सॉफ़्टवेयर लिखने के बारे में अधिक जान सकते हैं: https://www.udemy.com/enterprise-php/
आधार वर्गों के लिए संकेत या संदर्भ का उपयोग करने वाले कार्य यह जानने के बिना व्युत्पन्न वर्गों की वस्तुओं का उपयोग करने में सक्षम होना चाहिए।
जब मैंने पहली बार एलएसपी के बारे में पढ़ा, तो मैंने मान लिया कि यह बहुत सख्त अर्थों में था, अनिवार्य रूप से इसे इंटरफ़ेस कार्यान्वयन और टाइप-सुरक्षित कास्टिंग के बराबर किया गया था। इसका मतलब यह होगा कि एलएसपी या तो भाषा द्वारा ही सुनिश्चित है या नहीं। उदाहरण के लिए, इस सख्त अर्थ में, थ्रीडीबर्ड बोर्ड के लिए निश्चित रूप से प्रतिस्थापित करने योग्य है, जहां तक संकलक का संबंध है।
अवधारणा पर अधिक पढ़ने के बाद हालांकि मैंने पाया कि एलएसपी को आम तौर पर उससे अधिक व्यापक रूप से व्याख्या किया जाता है।
संक्षेप में, क्लाइंट कोड का "पता" करने के लिए इसका क्या मतलब है कि सूचक के पीछे की वस्तु एक व्युत्पन्न प्रकार की है, बजाय सूचक प्रकार के सुरक्षा के लिए प्रतिबंधित नहीं है। वस्तुओं के वास्तविक व्यवहार की जांच के माध्यम से एलएसपी का पालन भी परीक्षण योग्य है। यह है, किसी वस्तु की स्थिति और विधि कॉल के परिणामों पर विधि तर्कों के प्रभाव की जांच करना, या वस्तु से फेंके गए अपवादों के प्रकार।
उदाहरण के लिए फिर से जा रहे हैं, सिद्धांत में बोर्ड के तरीकों को थ्रीडीबर्ड पर ठीक काम करने के लिए बनाया जा सकता है। व्यवहार में, व्यवहार में अंतरों को रोकना बहुत कठिन होगा, जो कि क्लाइंट को ठीक से नहीं संभाल सकता है, बिना कार्यक्षमता के जो कि थर्डबॉडी को जोड़ने का इरादा रखता है।
हाथ में इस ज्ञान के साथ, एलएसपी पालन का मूल्यांकन यह निर्धारित करने में एक महान उपकरण हो सकता है कि जब संरचना विरासत के बजाय मौजूदा कार्यक्षमता को बढ़ाने के लिए अधिक उपयुक्त तंत्र है।
मुझे लगता है कि एलएसपी तकनीकी रूप से कवर किए गए सभी प्रकार के हैं: आप मूल रूप से उप-प्रकार के विवरण से दूर रहना चाहते हैं और सुपरपाइप का सुरक्षित रूप से उपयोग करना चाहते हैं।
तो लिस्कोव के 3 अंतर्निहित नियम हैं:
सिग्नेचर रूल: उपप्रकार के सुपरटेप के हर ऑपरेशन का एक सर्वमान्य रूप से वाक्यविन्यास होना चाहिए। कुछ संकलक आपके लिए जांचने में सक्षम होंगे। कम अपवादों को फेंकने और सुपरटेप तरीकों के रूप में कम से कम सुलभ होने के बारे में थोड़ा नियम है।
विधियाँ नियम: उन कार्यों का कार्यान्वयन शब्दार्थ ध्वनि है।
गुण नियम: यह व्यक्तिगत फ़ंक्शन कॉल से परे जाता है।
इन सभी गुणों को संरक्षित करने की आवश्यकता है और अतिरिक्त उपप्रकार कार्यक्षमता को सुपरटाइप गुणों का उल्लंघन नहीं करना चाहिए।
यदि इन तीन बातों का ध्यान रखा जाता है, तो आपने अंतर्निहित सामान से अलग कर दिया है और आप शिथिल युग्मित कोड लिख रहे हैं।
स्रोत: जावा में प्रोग्राम डेवलपमेंट - बारबरा लिस्कॉव
सॉफ्टवेयर परीक्षण में एलएसपी के उपयोग का एक महत्वपूर्ण उदाहरण है ।
अगर मेरे पास एक वर्ग ए है जो बी का एक एलएसपी-अनुपालन उपवर्ग है, तो मैं ए का परीक्षण करने के लिए बी के टेस्ट सूट का पुन: उपयोग कर सकता हूं।
उपवर्ग ए का पूरी तरह से परीक्षण करने के लिए, मुझे शायद कुछ और परीक्षण मामलों को जोड़ने की आवश्यकता है, लेकिन कम से कम मैं सभी सुपरक्लास बी के परीक्षण मामलों का पुन: उपयोग कर सकता हूं।
यह महसूस करने का एक तरीका यह है कि मैकग्रेगर ने "परीक्षण के लिए समानांतर पदानुक्रम" का निर्माण किया है: मेरा ATest
वर्ग इससे विरासत में मिला होगा BTest
। इंजेक्शन के कुछ रूप को तब टाइप बी की बजाय टेस्ट ए की वस्तुओं के साथ काम करने के लिए सुनिश्चित करने की आवश्यकता होती है (एक सरल टेम्पलेट विधि पैटर्न करेगा)।
ध्यान दें कि सभी उपवर्ग कार्यान्वयनों के लिए सुपर-टेस्ट सूट का पुन: उपयोग करना वास्तव में यह परीक्षण करने का एक तरीका है कि ये उपवर्ग कार्यान्वयन एलएसपी-अनुरूप हैं। इस प्रकार, कोई यह भी तर्क दे सकता है कि किसी भी उपवर्ग के संदर्भ में सुपरक्लास टेस्ट सूट चलाना चाहिए ।
Stackoverflow सवाल का जवाब भी देखें " क्या मैं इंटरफ़ेस के कार्यान्वयन का परीक्षण करने के लिए पुन: प्रयोज्य परीक्षणों की एक श्रृंखला को लागू कर सकता हूं? "
आइए जावा में उदाहरण देते हैं:
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
class Car extends TransportationDevice
{
@Override
void startEngine() { ... }
}
यहाँ कोई समस्या नहीं है, है ना? एक कार निश्चित रूप से एक परिवहन उपकरण है, और यहां हम देख सकते हैं कि यह अपने सुपरक्लास की शुरुआत () विधि को ओवरराइड करता है।
आइए एक और परिवहन उपकरण जोड़ें:
class Bicycle extends TransportationDevice
{
@Override
void startEngine() /*problem!*/
}
सब कुछ योजना के अनुसार नहीं हो रहा है! हां, एक साइकिल एक परिवहन उपकरण है, हालांकि, इसमें इंजन नहीं है और इसलिए, विधि startEngine () को लागू नहीं किया जा सकता है।
ये ऐसी समस्याएं हैं जो लिस्कोव सबस्टीट्यूशन सिद्धांत का उल्लंघन करती हैं, और वे आमतौर पर एक ऐसी विधि से पहचानी जा सकती हैं जो कुछ भी नहीं करती है, या लागू नहीं की जा सकती है।
इन समस्याओं का समाधान एक सही वंशानुक्रम पदानुक्रम है, और हमारे मामले में हम इंजन के साथ और बिना परिवहन उपकरणों के विभिन्न वर्गों को अलग करके समस्या का समाधान करेंगे। हालांकि एक साइकिल एक परिवहन उपकरण है, इसमें इंजन नहीं है। इस उदाहरण में परिवहन उपकरण की हमारी परिभाषा गलत है। इसमें इंजन नहीं होना चाहिए।
हम इस प्रकार के रूप में हमारे TransportDevice वर्ग refactor कर सकते हैं:
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
}
अब हम गैर-मोटर चालित उपकरणों के लिए TransportDevice का विस्तार कर सकते हैं।
class DevicesWithoutEngines extends TransportationDevice
{
void startMoving() { ... }
}
और मोटर चालित उपकरणों के लिए TransportDevice का विस्तार करें। इंजन ऑब्जेक्ट को जोड़ने के लिए यहां अधिक उपयुक्त है।
class DevicesWithEngines extends TransportationDevice
{
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
इस प्रकार हमारी कार क्लास अधिक विशिष्ट हो जाती है, जबकि लिस्कोव सबस्टीट्यूशन सिद्धांत का पालन करना।
class Car extends DevicesWithEngines
{
@Override
void startEngine() { ... }
}
और हमारा साइकिल वर्ग भी लिस्कोव सबस्टीट्यूशन सिद्धांत के अनुपालन में है।
class Bicycle extends DevicesWithoutEngines
{
@Override
void startMoving() { ... }
}
एलएसपी का यह सूत्रीकरण बहुत मजबूत है:
यदि टाइप S के प्रत्येक ऑब्जेक्ट o1 के लिए, T का ऑब्जेक्ट o2 है, तो ऐसे सभी प्रोग्रामों के लिए, जो P के लिए T के संदर्भ में हैं, P का व्यवहार अपरिवर्तित है, जब O1 को O2 के लिए प्रतिस्थापित किया जाता है, तो S, T का एक उपप्रकार है।
जिसका मूल रूप से मतलब है कि एस एक और है, पूरी तरह से टी के रूप में एक ही चीज के कार्यान्वयन को पूरी तरह से समझाया गया है और मैं बोल्ड हो सकता है और यह तय कर सकता हूं कि प्रदर्शन पी के व्यवहार का हिस्सा है ...
तो, मूल रूप से, देर से बाध्यकारी के किसी भी उपयोग एलएसपी का उल्लंघन करता है। जब हम किसी अन्य प्रकार के एक प्रकार के ऑब्जेक्ट को प्रतिस्थापित करते हैं, तो एक अलग व्यवहार प्राप्त करना OO का संपूर्ण बिंदु होता है!
विकिपीडिया द्वारा उद्धृत सूत्रीकरण बेहतर है क्योंकि संपत्ति संदर्भ पर निर्भर करती है और कार्यक्रम के पूरे व्यवहार को आवश्यक रूप से शामिल नहीं करती है।
एक बहुत ही सरल वाक्य में, हम कह सकते हैं:
बाल वर्ग को अपने आधार वर्ग की विशेषताओं का उल्लंघन नहीं करना चाहिए। यह इसके साथ सक्षम होना चाहिए। हम कह सकते हैं कि यह सबटाइपिंग के समान है।
लिस्कोव के प्रतिस्थापन सिद्धांत (एलएसपी)
हर समय हम एक प्रोग्राम मॉड्यूल डिजाइन करते हैं और हम कुछ वर्ग पदानुक्रम बनाते हैं। फिर हम कुछ वर्गों को कुछ व्युत्पन्न वर्गों का विस्तार करते हैं।
हमें यह सुनिश्चित करना चाहिए कि नई व्युत्पन्न कक्षाएं पुरानी कक्षाओं की कार्यक्षमता को बदलने के बिना ही विस्तारित हों। अन्यथा, नई कक्षाएं मौजूदा प्रोग्राम मॉड्यूल में उपयोग किए जाने पर अवांछित प्रभाव पैदा कर सकती हैं।
लिस्कोव के प्रतिस्थापन सिद्धांत में कहा गया है कि यदि कोई प्रोग्राम मॉड्यूल बेस क्लास का उपयोग कर रहा है, तो बेस क्लास के संदर्भ को प्रोग्राम मॉड्यूल की कार्यक्षमता को प्रभावित किए बिना एक व्युत्पन्न वर्ग से बदला जा सकता है।
उदाहरण:
नीचे क्लासिक उदाहरण है जिसके लिए लिस्कोव के प्रतिस्थापन सिद्धांत का उल्लंघन किया गया है। उदाहरण में, 2 वर्गों का उपयोग किया जाता है: आयत और वर्ग। आइए मान लें कि आयत वस्तु का उपयोग कहीं अनुप्रयोग में किया गया है। हम एप्लिकेशन का विस्तार करते हैं और स्क्वायर क्लास को जोड़ते हैं। वर्ग वर्ग को कुछ शर्तों के आधार पर, फ़ैक्टरी पैटर्न द्वारा लौटाया जाता है और हमें यह नहीं पता होता है कि किस प्रकार की वस्तु वापस आएगी। लेकिन हम जानते हैं कि यह एक आयत है। हम आयत वस्तु प्राप्त करते हैं, चौड़ाई 5 और ऊंचाई 10 पर सेट करते हैं और क्षेत्र प्राप्त करते हैं। चौड़ाई 5 और ऊंचाई 10 के साथ एक आयत के लिए, क्षेत्र 50 होना चाहिए। इसके बजाय, परिणाम 100 होगा
// Violation of Likov's Substitution Principle
class Rectangle {
protected int m_width;
protected int m_height;
public void setWidth(int width) {
m_width = width;
}
public void setHeight(int height) {
m_height = height;
}
public int getWidth() {
return m_width;
}
public int getHeight() {
return m_height;
}
public int getArea() {
return m_width * m_height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
m_width = width;
m_height = width;
}
public void setHeight(int height) {
m_width = height;
m_height = height;
}
}
class LspTest {
private static Rectangle getNewRectangle() {
// it can be an object returned by some factory ...
return new Square();
}
public static void main(String args[]) {
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// user knows that r it's a rectangle.
// It assumes that he's able to set the width and height as for the base
// class
System.out.println(r.getArea());
// now he's surprised to see that the area is 100 instead of 50.
}
}
निष्कर्ष:
यह सिद्धांत केवल ओपन क्लोज प्रिंसिपल का एक विस्तार है और इसका मतलब है कि हमें यह सुनिश्चित करना होगा कि नए व्युत्पन्न वर्ग अपने व्यवहार को बदले बिना आधार वर्गों का विस्तार कर रहे हैं।
इसे भी देखें: ओपन प्रिंसिपल
बेहतर संरचना के लिए कुछ समान अवधारणाएं: कॉन्फ़िगरेशन पर कन्वेंशन
कुछ परिशिष्ट:
मुझे आश्चर्य है कि किसी भी व्यक्ति ने आधार वर्ग के इनवेरियंट, प्रीकॉन्डिशंस और पोस्ट की शर्तों के बारे में क्यों नहीं लिखा जो कि व्युत्पन्न वर्गों द्वारा पालन किया जाना चाहिए। एक व्युत्पन्न वर्ग डी के लिए बेस क्लास बी द्वारा पूरी तरह से उपयोगी होने के लिए, क्लास डी को कुछ शर्तों का पालन करना चाहिए:
इसलिए व्युत्पन्न को आधार वर्ग द्वारा लगाई गई उपरोक्त तीन स्थितियों से अवगत होना चाहिए। इसलिए, उपप्रकार के नियम पूर्व-निर्धारित हैं। जिसका अर्थ है, 'आईएस ए' संबंध केवल तभी माना जाएगा जब कुछ नियमों को उपप्रकार द्वारा पालन किया जाता है। इन नियमों को, अपरिवर्तनीयों के रूप में, पूर्वगामी और पोस्टकंडिशन, एक औपचारिक ' डिजाइन अनुबंध ' द्वारा तय किया जाना चाहिए ।
मेरे ब्लॉग पर उपलब्ध इस पर आगे की चर्चा: लिस्कोव प्रतिस्थापन सिद्धांत
एलएसपी सरल शब्दों में कहता है कि एक ही सुपरक्लास की वस्तुओं को एक -दूसरे के साथ बिना किसी चीज को तोड़ने के लिए स्वैप किया जाना चाहिए ।
उदाहरण के लिए, यदि हमारे पास एक Cat
और एक Dog
वर्ग से व्युत्पन्न Animal
वर्ग है, तो पशु वर्ग का उपयोग करने वाले किसी भी कार्य को सामान्य रूप से उपयोग Cat
या Dog
व्यवहार करने में सक्षम होना चाहिए ।
बोर्ड की एक सरणी के संदर्भ में थ्रीडीबर्ड को लागू करना क्या उपयोगी होगा?
शायद आप बोर्ड के रूप में विभिन्न विमानों में थ्री डी बोर्ड के स्लाइस का इलाज करना चाहते हैं। उस मामले में आप बोर्ड के लिए एक इंटरफ़ेस (या अमूर्त वर्ग) को अमल में लाना चाहते हैं ताकि कई कार्यान्वयनों की अनुमति मिल सके।
बाहरी इंटरफ़ेस के संदर्भ में, आप TwoDBoard और ThreeDBoard दोनों के लिए एक बोर्ड इंटरफ़ेस को फ़ैक्टर करना चाह सकते हैं (हालाँकि उपरोक्त विधियों में से कोई भी उपयुक्त नहीं है)।
एक वर्ग एक आयत है जहाँ चौड़ाई ऊँचाई के बराबर होती है। यदि वर्ग चौड़ाई और ऊँचाई के लिए दो अलग-अलग आकार निर्धारित करता है तो यह वर्ग अशुद्धि का उल्लंघन करता है। यह साइड इफेक्ट्स शुरू करके चारों ओर काम किया है। लेकिन अगर आयत में पूर्व निर्धारित 0 <ऊंचाई और 0 <चौड़ाई के साथ एक सेटसाइज़ (ऊंचाई, चौड़ाई) थी। व्युत्पन्न उपप्रकार विधि में ऊंचाई == चौड़ाई की आवश्यकता होती है; एक मजबूत पूर्व शर्त (और जो lsp का उल्लंघन करता है)। इससे पता चलता है कि हालांकि वर्ग एक आयत है, यह एक मान्य उपप्रकार नहीं है क्योंकि पूर्व शर्त मजबूत हो जाती है। आस-पास के कार्य (सामान्य रूप से एक बुरी चीज) एक साइड इफेक्ट का कारण बनते हैं और इससे पोस्ट की स्थिति कमजोर हो जाती है (जो एलएसपी का उल्लंघन करती है)। आधार पर setWidth में पोस्ट की स्थिति 0 <चौड़ाई है। व्युत्पन्न इसे ऊंचाई == चौड़ाई के साथ कमजोर करता है।
इसलिए एक आकार बदलने योग्य वर्ग एक आकार बदलने योग्य आयत नहीं है।
यह सिद्धांत 1987 में बारबरा लिस्कोव द्वारा पेश किया गया था और एक सुपरक्लास और इसके उपप्रकारों के व्यवहार पर ध्यान केंद्रित करके ओपन-क्लोज्ड सिद्धांत का विस्तार करता है।
इसका महत्व तब स्पष्ट हो जाता है जब हम इसका उल्लंघन करने के परिणामों पर विचार करते हैं। निम्नलिखित वर्ग का उपयोग करने वाले एप्लिकेशन पर विचार करें।
public class Rectangle
{
private double width;
private double height;
public double Width
{
get
{
return width;
}
set
{
width = value;
}
}
public double Height
{
get
{
return height;
}
set
{
height = value;
}
}
}
कल्पना कीजिए कि एक दिन, ग्राहक आयतों के अलावा वर्गों में हेरफेर करने की क्षमता की मांग करता है। चूंकि एक वर्ग एक आयत है, वर्ग वर्ग को आयत वर्ग से लिया जाना चाहिए।
public class Square : Rectangle
{
}
हालाँकि, ऐसा करने से हम दो समस्याओं का सामना करेंगे:
एक वर्ग को आयत से विरासत में मिली ऊँचाई और चौड़ाई दोनों प्रकार के चर की आवश्यकता नहीं होती है और यह स्मृति में महत्वपूर्ण बर्बादी पैदा कर सकता है यदि हमें सैकड़ों हजारों वर्ग ऑब्जेक्ट बनाने हैं। आयत से विरासत में मिली चौड़ाई और ऊंचाई सेटर गुण एक वर्ग के लिए अनुपयुक्त हैं क्योंकि एक वर्ग की चौड़ाई और ऊंचाई समान हैं। समान मान के लिए ऊंचाई और चौड़ाई दोनों सेट करने के लिए, हम दो नए गुण बना सकते हैं:
public class Square : Rectangle
{
public double SetWidth
{
set
{
base.Width = value;
base.Height = value;
}
}
public double SetHeight
{
set
{
base.Height = value;
base.Width = value;
}
}
}
अब, जब कोई एक वर्ग वस्तु की चौड़ाई निर्धारित करेगा, तो इसकी ऊंचाई तदनुसार और इसके विपरीत बदल जाएगी।
Square s = new Square();
s.SetWidth(1); // Sets width and height to 1.
s.SetHeight(2); // sets width and height to 2.
आइए आगे बढ़ते हैं और इस अन्य फ़ंक्शन पर विचार करते हैं:
public void A(Rectangle r)
{
r.SetWidth(32); // calls Rectangle.SetWidth
}
यदि हम इस फ़ंक्शन में एक वर्ग ऑब्जेक्ट का संदर्भ देते हैं, तो हम एलएसपी का उल्लंघन करेंगे क्योंकि फ़ंक्शन अपने तर्कों के व्युत्पन्न के लिए काम नहीं करता है। गुण चौड़ाई और ऊंचाई पॉलीमॉर्फिक नहीं हैं क्योंकि वे आयत में आभासी घोषित नहीं किए जाते हैं (वर्ग ऑब्जेक्ट दूषित हो जाएगा क्योंकि ऊंचाई बदल नहीं जाएगी)।
हालांकि, सेटर के गुणों को आभासी घोषित करने से हमें एक और उल्लंघन का सामना करना पड़ेगा, ओ.सी.पी. वास्तव में, एक व्युत्पन्न वर्ग वर्ग का निर्माण आधार वर्ग आयत में परिवर्तन का कारण बन रहा है।
एलएसपी के लिए मुझे अब तक का सबसे स्पष्ट स्पष्टीकरण "लिस्कोव सबस्टीट्यूशन सिद्धांत कहता है कि एक व्युत्पन्न वर्ग का ऑब्जेक्ट सिस्टम में किसी भी त्रुटि को लाए बिना बेस क्लास के ऑब्जेक्ट को बदलने या बेस क्लास के व्यवहार को संशोधित करने में सक्षम होना चाहिए। “ यहाँ से । लेख एलएसपी का उल्लंघन करने और इसे ठीक करने के लिए कोड उदाहरण देता है।
मान लीजिए कि हम अपने कोड में एक आयत का उपयोग करते हैं
r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);
हमारे ज्यामिति वर्ग में हमने जाना कि एक वर्ग एक विशेष प्रकार का आयत है क्योंकि इसकी चौड़ाई इसकी ऊँचाई जितनी है। आइए Square
इस जानकारी के आधार पर एक क्लास बनाएं :
class Square extends Rectangle {
setDimensions(width, height){
assert(width == height);
super.setDimensions(width, height);
}
}
अगर हम बदलने के Rectangle
साथ Square
हमारा पहला कोड में है, तो यह टूट जाएगा:
r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);
इसका कारण यह है कि Square
एक नई पूर्व शर्त है कि हमारे पास Rectangle
कक्षा में नहीं है width == height
:। एलएसपी के अनुसार Rectangle
उदाहरणों को Rectangle
उपवर्ग उदाहरणों के साथ प्रतिस्थापित किया जाना चाहिए । ऐसा इसलिए है क्योंकि ये उदाहरण इंस्टेंसेस के लिए टाइप चेक पास करते हैं Rectangle
और इसलिए वे आपके कोड में अप्रत्याशित त्रुटियां पैदा करेंगे।
यह विकी लेख में "पूर्व शर्त को एक उपप्रकार में मजबूत नहीं किया जा सकता" भाग के लिए एक उदाहरण था । इसलिए योग करने के लिए, एलएसपी का उल्लंघन करना संभवत: आपके कोड में कुछ बिंदु पर त्रुटियों का कारण होगा।
एलएसपी का कहना है कि '' वस्तुओं को उनके उपप्रकारों द्वारा बदली जानी चाहिए ''। दूसरी ओर, यह सिद्धांत इंगित करता है
चाइल्ड क्लासेस को पैरेंट क्लास की टाइप परिभाषाओं को कभी नहीं तोड़ना चाहिए।
और निम्न उदाहरण एलएसपी की बेहतर समझ रखने में मदद करता है।
एलएसपी के बिना:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
return; //it isn`t rendered in this case
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
एलएसपी द्वारा फिक्सिंग:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
showAd();//it has a specific behavior based on its requirement
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
मैं आपको लेख पढ़ने के लिए प्रोत्साहित करता हूं: लिज़कोव प्रतिस्थापन सिद्धांत (एलएसपी) का उल्लंघन ।
आप एक स्पष्टीकरण पा सकते हैं कि लिस्कोव सबस्टीट्यूशन सिद्धांत क्या है, सामान्य सुराग आपको यह अनुमान लगाने में मदद करते हैं कि क्या आपने पहले ही इसका उल्लंघन किया है और दृष्टिकोण का एक उदाहरण जो आपको अपनी कक्षा के पदानुक्रम को अधिक सुरक्षित बनाने में मदद करेगा।
LISKOV SUBSTITUTION PRINCIPLE (मार्क सेमेन बुक से) कहता है कि हमें किसी क्लाइंट या तोड़े बिना किसी इंटरफ़ेस के एक कार्यान्वयन को बदलने में सक्षम होना चाहिए। यह सिद्धांत जो भविष्य में होने वाली आवश्यकताओं को पूरा करने में सक्षम बनाता है, भले ही हम ' टी आज उन्हें पसंद है।
यदि हम कंप्यूटर को दीवार (कार्यान्वयन) से अनप्लग करते हैं, तो न तो दीवार आउटलेट (इंटरफ़ेस) और न ही कंप्यूटर (क्लाइंट) टूट जाता है (वास्तव में, अगर यह लैपटॉप कंप्यूटर है, तो यह कुछ समय के लिए बैटरी पर भी चल सकता है) । सॉफ्टवेयर के साथ, हालांकि, एक ग्राहक अक्सर एक सेवा उपलब्ध होने की उम्मीद करता है। यदि सेवा हटा दी गई थी, तो हमें NullReferenceException मिलती है। इस प्रकार की स्थिति से निपटने के लिए, हम एक इंटरफ़ेस का कार्यान्वयन बना सकते हैं जो "कुछ भी नहीं" करता है। यह एक डिज़ाइन पैटर्न है जिसे नल ऑब्जेक्ट [4] के रूप में जाना जाता है और यह दीवार से कंप्यूटर को अनप्लग करने के लिए मोटे तौर पर मेल खाता है। क्योंकि हम ढीले युग्मन का उपयोग कर रहे हैं, हम वास्तविक कार्यान्वयन को किसी ऐसी चीज़ से बदल सकते हैं जो बिना परेशानी के कुछ भी नहीं करती है।
लिकोव के प्रतिस्थापन सिद्धांत में कहा गया है कि यदि कोई प्रोग्राम मॉड्यूल बेस क्लास का उपयोग कर रहा है, तो बेस क्लास के संदर्भ को प्रोग्राम मॉड्यूल की कार्यक्षमता को प्रभावित किए बिना एक व्युत्पन्न वर्ग से बदला जा सकता है।
आशय - व्युत्पन्न प्रकारों को उनके आधार प्रकारों के लिए पूरी तरह से सक्षम होना चाहिए।
उदाहरण - जावा में सह-वैरिएंट वापसी प्रकार।
यहाँ इस पोस्ट से एक अंश है जो चीजों को अच्छी तरह से स्पष्ट करता है:
[..] कुछ सिद्धांतों को समझने के लिए, इसका उल्लंघन होने पर महसूस करना महत्वपूर्ण है। अब मैं यही करूंगा।
इस सिद्धांत के उल्लंघन का क्या अर्थ है? तात्पर्य यह है कि एक वस्तु एक इंटरफेस के साथ व्यक्त किए गए अमूर्त द्वारा लगाए गए अनुबंध को पूरा नहीं करती है। दूसरे शब्दों में, इसका मतलब है कि आपने अपने अमूर्त को गलत पहचान लिया है।
निम्नलिखित उदाहरण पर विचार करें:
interface Account
{
/**
* Withdraw $money amount from this account.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
class DefaultAccount implements Account
{
private $balance;
public function withdraw(Money $money)
{
if (!$this->enoughMoney($money)) {
return;
}
$this->balance->subtract($money);
}
}
क्या यह एलएसपी का उल्लंघन है? हाँ। ऐसा इसलिए है क्योंकि खाते का अनुबंध हमें बताता है कि एक खाता वापस ले लिया जाएगा, लेकिन यह हमेशा ऐसा नहीं होता है। तो, इसे ठीक करने के लिए मुझे क्या करना चाहिए? मैं सिर्फ अनुबंध को संशोधित करता हूं:
interface Account
{
/**
* Withdraw $money amount from this account if its balance is enough.
* Otherwise do nothing.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
Voilà, अब अनुबंध संतुष्ट है।
यह सूक्ष्म उल्लंघन अक्सर एक ग्राहक को नियोजित ठोस वस्तुओं के बीच अंतर बताने की क्षमता के साथ लगाता है। उदाहरण के लिए, पहले खाते के अनुबंध को देखते हुए, यह निम्नलिखित की तरह लग सकता है:
class Client
{
public function go(Account $account, Money $money)
{
if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
return;
}
$account->withdraw($money);
}
}
और, यह स्वचालित रूप से खुले-बंद सिद्धांत का उल्लंघन करता है [अर्थात, धन निकासी की आवश्यकता के लिए। क्योंकि आप कभी नहीं जानते कि क्या होता है यदि अनुबंध का उल्लंघन करने वाली वस्तु में पर्याप्त पैसा नहीं है। शायद यह सिर्फ कुछ नहीं लौटाता है, शायद एक अपवाद फेंक दिया जाएगा। तो आपको यह जांचना होगा कि क्या यह hasEnoughMoney()
- जो एक इंटरफ़ेस का हिस्सा नहीं है। तो यह मजबूर ठोस-वर्ग पर निर्भर जांच एक OCP उल्लंघन है]।
यह बिंदु एक गलत धारणा को भी संबोधित करता है कि मैं एलएसपी उल्लंघन के बारे में अक्सर सामना करता हूं। इसमें कहा गया है, "यदि किसी बच्चे में माता-पिता का व्यवहार बदल गया है, तो वह एलएसपी का उल्लंघन करता है।" हालाँकि, ऐसा नहीं है - जब तक कि कोई बच्चा अपने माता-पिता के अनुबंध का उल्लंघन नहीं करता है।