SOLID सिद्धांतों का विषय और, सामान्य रूप से, कोड सफाई को एक बार Habré पर एक से अधिक बार उठाया गया है, और, शायद, यह पहले से ही अच्छी तरह से कूच किया गया है। लेकिन फिर भी, बहुत समय पहले मुझे एक दिलचस्प आईटी कंपनी को साक्षात्कार से गुजरना पड़ा था, जहां मुझे उदाहरणों और स्थितियों के साथ SOLID सिद्धांतों के बारे में बात करने के लिए कहा गया था, जब मैंने इन सिद्धांतों का पालन नहीं किया और इसके कारण क्या हुआ। और उस पल मुझे एहसास हुआ कि कुछ अवचेतन स्तर पर मैं इन सिद्धांतों को समझता हूं और यहां तक कि उन सभी को नाम दे सकता हूं, लेकिन मेरे लिए संक्षिप्त और समझने योग्य उदाहरण देने के लिए एक समस्या बन गई। इसलिए, मैंने अपने और समुदाय के लिए यह भी बेहतर समझ के लिए ठोस सिद्धांतों पर जानकारी को सामान्य बनाने का फैसला किया। यह लेख उन लोगों के लिए उपयोगी होना चाहिए जो केवल SOLID सिद्धांतों से परिचित हों, साथ ही साथ उन लोगों के लिए भी जो SOLID सिद्धांतों पर "कुत्ता खा गए"।
उन लोगों के लिए जो सिद्धांतों से परिचित हैं और केवल उनकी और उनके उपयोग की स्मृति को ताज़ा करना चाहते हैं, आप लेख के अंत में धोखा पत्र पर दाएं मुड़ सकते हैं।
ठोस सिद्धांत क्या हैं? विकिपीडिया की परिभाषा के अनुसार, यह है:
ऑब्जेक्ट-ओरिएंटेड डिज़ाइन में वर्ग डिज़ाइन के पांच बुनियादी सिद्धांतों का संक्षिप्त नाम - एस इंगले जिम्मेदारी, ओ पेन-क्लोज्ड, एल आइसकोव प्रतिस्थापन, आई नेटरफेस अलगाव और डी एपेंडेंसी उलटा।
इस प्रकार, हमारे पास 5 सिद्धांत हैं, जिन पर हम नीचे विचार करेंगे:
- एकल जिम्मेदारी सिद्धांत
- खुलेपन / बंद करने का सिद्धांत (ओपन-बंद)
- प्रतिस्थापन का सिद्धांत बारबरा लिस्कोव (लिस्कोव प्रतिस्थापन)
- इंटरफ़ेस अलगाव
- निर्भरता इनवर्शन सिद्धांत
एकल जिम्मेदारी सिद्धांत
इसलिए, एक उदाहरण के रूप में, आइए एक लोकप्रिय और व्यापक रूप से उपयोग किया जाने वाला उदाहरण लें - ऑर्डर, माल और ग्राहकों के साथ एक ऑनलाइन स्टोर।
एकमात्र जिम्मेदारी का सिद्धांत पढ़ता है:
"प्रत्येक वस्तु को एक ही जिम्मेदारी सौंपी जानी चाहिए ।
" यानी दूसरे शब्दों में, एक विशेष वर्ग को एक विशिष्ट समस्या का समाधान करना चाहिए - न तो अधिक और न ही कम।
ऑनलाइन स्टोर में ऑर्डर प्रस्तुत करने के लिए निम्नलिखित वर्ग विवरण पर विचार करें:
class Order { public function calculateTotalSum(){} public function getItems(){} public function getItemCount(){} public function addItem($item){} public function deleteItem($item){} public function printOrder(){} public function showOrder(){} public function load(){} public function save(){} public function update(){} public function delete(){} }
जैसा कि आप देख सकते हैं, यह वर्ग 3 अलग-अलग प्रकार के कार्यों के लिए ऑपरेशन करता है: ऑर्डर के साथ ही काम करना (
calculateTotalSum, getItems, getItemsCount, addItem, deleteItem
), ऑर्डर को प्रदर्शित करना (
printOrder, showOrder
) और डेटा स्टोर के साथ काम करना (
load, save, update, delete
)।
इससे क्या हो सकता है?
यह इस तथ्य की ओर जाता है कि अगर हम मुद्रण विधियों या स्टोर के संचालन में बदलाव करना चाहते हैं, तो हम ऑर्डर के वर्ग को ही बदल देते हैं, जिससे इसकी निष्क्रियता हो सकती है।
इस समस्या को हल करने के लिए इस वर्ग का 3 अलग-अलग वर्गों में विभाजन है, जिनमें से प्रत्येक अपने कार्य में लगा रहेगा।
class Order { public function calculateTotalSum(){} public function getItems(){} public function getItemCount(){} public function addItem($item){} public function deleteItem($item){} } class OrderRepository { public function load($orderID){} public function save($order){} public function update($order){} public function delete($order){} } class OrderViewer { public function printOrder($order){} public function showOrder($order){} }
अब प्रत्येक वर्ग अपने विशिष्ट कार्य में लगा हुआ है और प्रत्येक वर्ग के लिए इसे बदलने का केवल 1 कारण है।
खुलेपन / बंद करने का सिद्धांत (ओपन-बंद)
यह सिद्धांत कहता है -
" , "
।
" , "
सरल शब्दों में, इसे निम्नानुसार वर्णित किया जा सकता है - सभी वर्ग, कार्य आदि। डिज़ाइन किया जाना चाहिए ताकि उनके व्यवहार को बदलने के लिए, हमें उनके स्रोत कोड को बदलने की आवश्यकता न हो।
OrderRepository
वर्ग के उदाहरण पर विचार करें।
class OrderRepository { public function load($orderID) { $pdo = new PDO($this->config->getDsn(), $this->config->getDBUser(), $this->config->getDBPassword()); $statement = $pdo->prepare('SELECT * FROM `orders` WHERE id=:id'); $statement->execute(array(':id' => $orderID)); return $query->fetchObject('Order'); } public function save($order){} public function update($order){} public function delete($order){} }
इस मामले में, रिपॉजिटरी डेटाबेस है। जैसे MySQL लेकिन अचानक हम अपने ऑर्डर डेटा को लोड करना चाहते थे, उदाहरण के लिए, एक तृतीय-पक्ष सर्वर के एपीआई के माध्यम से, जो उदाहरण के लिए, 1 सी के साथ डेटा लेता है। हमें किन बदलावों की आवश्यकता होगी? उदाहरण के लिए, कई विकल्प हैं, सीधे
OrderRepository
वर्ग के तरीकों को
OrderRepository
, लेकिन यह एक
खुले / बंद सिद्धांत का अनुपालन नहीं करता है, क्योंकि कक्षा संशोधन के लिए बंद है, और पहले से ही अच्छी तरह से काम कर रहे वर्ग के लिए परिवर्तन करना अवांछनीय है। इसका मतलब यह है कि आप
OrderRepository
वर्ग से विरासत में ले सकते हैं और सभी तरीकों को ओवरराइड कर सकते हैं, लेकिन यह समाधान सबसे अच्छा नहीं है, क्योंकि जब आप
OrderRepository
एक विधि
OrderRepository
तो हमें इसके सभी वंशों के लिए समान तरीके जोड़ने होंगे। इसलिए,
खुलेपन / निकटता के
सिद्धांत को पूरा करने के लिए, निम्नलिखित समाधान को लागू करना बेहतर है - एक
IOrderSource
इंटरफ़ेस बनाएं जो कि संबंधित वर्ग
MySQLOrderSource
,
ApiOrderSource
और इसी तरह से लागू किया जाएगा।
IOrderSource इंटरफ़ेस और इसका कार्यान्वयन और उपयोग class OrderRepository { private $source; public function setSource(IOrderSource $source) { $this->source = $source; } public function load($orderID) { return $this->source->load($orderID); } public function save($order){} public function update($order){} } interface IOrderSource { public function load($orderID); public function save($order); public function update($order); public function delete($order); } class MySQLOrderSource implements IOrderSource { public function load($orderID); public function save($order){} public function update($order){} public function delete($order){} } class ApiOrderSource implements IOrderSource { public function load($orderID); public function save($order){} public function update($order){} public function delete($order){} }
इस प्रकार, हम स्रोत को बदल सकते हैं और तदनुसार
OrderRepository
क्लास के लिए व्यवहार को सेट कर सकते हैं, जिसे हमें
OrderRepository
क्लास को बदलने के बिना,
IOrderSource
लागू करने की आवश्यकता है।
प्रतिस्थापन बारबरा लिस्कोव का सिद्धांत (लिस्कोव प्रतिस्थापन)
शायद वह सिद्धांत जो समझने में सबसे बड़ी कठिनाइयों का कारण बनता है।
सिद्धांत पढ़ता है:
"कार्यक्रम के गुणों को कार्यक्रम के गुणों को बदलने के बिना उनके वारिसों द्वारा प्रतिस्थापित किया जा सकता है ।
" अपने शब्दों में, मैं यह कहूँगा - जब वर्ग अनुवर्ती का उपयोग कर रहा है, तो कोड निष्पादन का परिणाम पूर्वानुमेय होना चाहिए और विधि के गुणों को नहीं बदलना चाहिए।
दुर्भाग्य से, मैं ऑनलाइन स्टोर के ढांचे के भीतर इस सिद्धांत के लिए एक सुलभ उदाहरण के साथ नहीं आ सका, लेकिन ज्यामितीय आकृतियों और क्षेत्र की गणना के पदानुक्रम के साथ एक क्लासिक उदाहरण है। नीचे उदाहरण कोड।
एक आयत और वर्ग का एक पदानुक्रम का उदाहरण और उनके क्षेत्र की गणना class Rectangle { protected $width; protected $height; public setWidth($width) { $this->width = $width; } public setHeight($height) { $this->height = $height; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } } class Square extends Rectangle { public setWidth($width) { parent::setWidth($width); parent::setHeight($width); } public setHeight($height) { parent::setHeight($height); parent::setWidth($height); } } function calculateRectangleSquare(Rectangle $rectangle, $width, $height) { $rectangle->setWidth($width); $rectangle->setHeight($height); return $rectangle->getHeight * $rectangle->getWidth; } calculateRectangleSquare(new Rectangle, 4, 5);
जाहिर है, इस तरह के कोड को स्पष्ट रूप से निष्पादित नहीं किया गया है जैसा कि इससे अपेक्षित है।
लेकिन समस्या क्या है? एक "वर्ग" एक "आयत" नहीं है? यह है, लेकिन ज्यामितीय शब्दों में। वस्तुओं के संदर्भ में, एक वर्ग एक आयत नहीं है, क्योंकि "वर्ग" ऑब्जेक्ट का व्यवहार "आयत" ऑब्जेक्ट के व्यवहार के अनुरूप नहीं है।
फिर समस्या को कैसे हल किया जाए?
समाधान
अनुबंध द्वारा डिजाइन के रूप में इस तरह की अवधारणा से निकटता से संबंधित है। अनुबंध द्वारा डिजाइन का विवरण एक से अधिक लेख ले सकता है, इसलिए हम खुद को उन विशेषताओं तक सीमित
रखते हैं जो लिस्क सिद्धांत से संबंधित हैं।
अनुबंध का डिज़ाइन कुछ प्रतिबंधों की ओर जाता है कि कैसे अनुबंध विरासत के साथ बातचीत कर सकते हैं, अर्थात्:
- उपवर्गों में पूर्वधारणाओं को मजबूत नहीं किया जा सकता है।
- उपवर्गों में पोस्टकंडिशन में ढील नहीं दी जा सकती।
"अन्य पूर्व और पोस्टकंडिशन क्या हैं?" आप पूछ सकते हैं।
उत्तर :
पूर्व -
निर्धारण वे हैं जो विधि को कॉल करने से पहले कॉलर द्वारा किया जाना चाहिए,
पोस्टकॉन्डिशन वे हैं जिन्हें कॉल विधि द्वारा गारंटी दी गई है।
आइए हम अपने उदाहरण पर लौटें और देखें कि हमने पूर्व और बाद के बदलावों को कैसे बदला।
ऊंचाई और चौड़ाई निर्धारित करने के तरीकों को कॉल करते समय हमने किसी भी पूर्व शर्त का उपयोग नहीं किया, लेकिन हमने उत्तराधिकारी वर्ग में पोस्टकंडिशन को बदल दिया और कमजोर लोगों को बदल दिया, जो लिस्कोव सिद्धांत के अनुसार असंभव था।
इसलिए हमने उन्हें कमजोर किया है। यदि
setWidth
पद्धति के उत्तरार्ध के लिए
setWidth
(($this->width == $width) && ($this->height == $oldHeight))
(
$oldHeight
हमने शुरुआत में निर्धारित विधि से निर्धारित किया है), यह स्थिति बच्चे की कक्षा में संतुष्ट नहीं है और तदनुसार, हमने इसे कमजोर कर दिया और
उल्लंघन किया।
इसलिए, OOP के ढांचे के भीतर "वर्गाकार" पदानुक्रम को "आयत" बनाना और एक आकृति के क्षेत्र की गणना करने के कार्य के लिए बेहतर है, लेकिन उन्हें 2 अलग-अलग संस्थाओं के रूप में बनाने के लिए बेहतर नहीं है:
class Rectangle { protected $width; protected $height; public setWidth($width) { $this->width = $width; } public setHeight($height) { $this->height = $height; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } } class Square { protected $size; public setSize($size) { $this->size = $size; } public function getSize() { return $this->size; } }
लिस्कॉ सिद्धांत के गैर-पालन और इस संबंध में लिए गए निर्णय का एक अच्छा
वास्तविक उदाहरण रॉबर्ट मार्टिन की पुस्तक "क्विक प्रोग्राम डेवलपमेंट" सेक्शन "लिस्को सब्सट्रेशन प्रिंसिपल" में माना जाता है। एक वास्तविक उदाहरण। ”
इंटरफ़ेस अलगाव
यह सिद्धांत कहता है कि
"कई विशिष्ट इंटरफेस एक सार्वभौमिक से बेहतर हैं"इस सिद्धांत का अनुपालन आवश्यक है ताकि इंटरफ़ेस का उपयोग / लागू करने वाले ग्राहक वर्ग केवल उन विधियों के बारे में जान सकें जो वे उपयोग करते हैं, जो अप्रयुक्त कोड की मात्रा में कमी की ओर जाता है।
चलो ऑनलाइन स्टोर के उदाहरण पर वापस जाते हैं।
मान लें कि हमारे उत्पादों में प्रचारक कोड, छूट हो सकती है, उनके पास किसी प्रकार की कीमत, शर्त आदि होती है। यदि यह कपड़े है, तो यह किस सामग्री, रंग और आकार से बना है, इसके लिए निर्धारित है।
हम निम्नलिखित इंटरफ़ेस का वर्णन करते हैं
interface IItem { public function applyDiscount($discount); public function applyPromocode($promocode); public function setColor($color); public function setSize($size); public function setCondition($condition); public function setPrice($price); }
यह इंटरफ़ेस इस मायने में बुरा है कि इसमें बहुत सारी विधियाँ शामिल हैं। लेकिन क्या होगा यदि हमारे माल के वर्ग में छूट या प्रचार कोड नहीं हो सकते हैं, या यह उस सामग्री को स्थापित करने के लिए इसका कोई मतलब नहीं है, जिससे इसे बनाया गया है (उदाहरण के लिए, पुस्तकों के लिए)। इस प्रकार, प्रत्येक कक्षा में अप्रयुक्त तरीकों को लागू नहीं करने के लिए, इंटरफ़ेस को कई छोटे लोगों में विभाजित करना और प्रत्येक विशिष्ट वर्ग के साथ आवश्यक इंटरफेस को लागू करना बेहतर होता है।
IItem इंटरफ़ेस को कई में विभाजित करना interface IItem { public function setCondition($condition); public function setPrice($price); } interface IClothes { public function setColor($color); public function setSize($size); public function setMaterial($material); } interface IDiscountable { public function applyDiscount($discount); public function applyPromocode($promocode); } class Book implemets IItem, IDiscountable { public function setCondition($condition){} public function setPrice($price){} public function applyDiscount($discount){} public function applyPromocode($promocode){} } class KidsClothes implemets IItem, IClothes { public function setCondition($condition){} public function setPrice($price){} public function setColor($color){} public function setSize($size){} public function setMaterial($material){} }
निर्भरता इनवर्शन सिद्धांत
सिद्धांत पढ़ता है:
“व्यवस्था के भीतर निर्भरताएँ अमूर्तताओं के आधार पर निर्मित होती हैं। शीर्ष स्तर के मॉड्यूल निचले स्तर के मॉड्यूल से स्वतंत्र होते हैं। विवरण विवरण पर निर्भर नहीं होना चाहिए। विवरण सार पर निर्भर होना चाहिए ।
” इस परिभाषा को छोटा किया जा सकता है -
"निर्भरता को अमूर्तता के संबंध में बनाया जाना चाहिए, विवरण पर नहीं ।
"उदाहरण के लिए, किसी खरीदार द्वारा ऑर्डर के भुगतान पर विचार करें।
class Customer { private $currentOrder = null; public function buyItems() { if(is_null($this->currentOrder)){ return false; } $processor = new OrderProcessor(); return $processor->checkout($this->currentOrder); } public function addItem($item){ if(is_null($this->currentOrder)){ $this->currentOrder = new Order(); } return $this->currentOrder->addItem($item); } public function deleteItem($item){ if(is_null($this->currentOrder)){ return false; } return $this->currentOrder ->deleteItem($item); } } class OrderProcessor { public function checkout($order){} }
सब कुछ काफी तार्किक और तार्किक लगता है। लेकिन एक समस्या है -
Customer
वर्ग
OrderProcessor
वर्ग पर निर्भर करता है (इसके अलावा, खुला / बंद सिद्धांत संतुष्ट नहीं है)।
किसी विशेष वर्ग पर निर्भरता से छुटकारा पाने के लिए,
Customer
को अमूर्तता पर निर्भर बनाना आवश्यक है, अर्थात्।
IOrderProcessor
इंटरफ़ेस से। यह निर्भरता बसने वालों, विधि मापदंडों या
Dependency Injection
कंटेनर के माध्यम से इंजेक्ट की जा सकती है। मैंने विधि 2 पर ध्यान केंद्रित करने का निर्णय लिया और निम्नलिखित कोड प्राप्त किया।
ग्राहक वर्ग की निर्भरता को प्राप्त करना class Customer { private $currentOrder = null; public function buyItems(IOrderProcessor $processor) { if(is_null($this->currentOrder)){ return false; } return $processor->checkout($this->currentOrder); } public function addItem($item){ if(is_null($this->currentOrder)){ $this->currentOrder = new Order(); } return $this->currentOrder->addItem($item); } public function deleteItem($item){ if(is_null($this->currentOrder)){ return false; } return $this->currentOrder ->deleteItem($item); } } interface IOrderProcessor { public function checkout($order); } class OrderProcessor implements IOrderProcessor { public function checkout($order){} }
इस प्रकार,
Customer
वर्ग अब केवल अमूर्त और ठोस कार्यान्वयन पर निर्भर करता है, अर्थात्। उसके लिए विवरण इतना महत्वपूर्ण नहीं है।
धोखा की चादर
उपरोक्त सभी को संक्षेप में, मैं निम्नलिखित धोखा पत्र बनाना चाहूंगा
- एकल जिम्मेदारी सिद्धांत
"प्रत्येक वस्तु को एक ही जिम्मेदारी सौंपी जानी चाहिए"
ऐसा करने के लिए, हम जांचते हैं कि कक्षा बदलने के हमारे पास कितने कारण हैं - यदि एक से अधिक हैं, तो इस वर्ग को तोड़ दिया जाना चाहिए।
- खुलेपन / बंद करने का सिद्धांत (ओपन-बंद)
"सॉफ्टवेयर इकाइयां विस्तार के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए बंद"
ऐसा करने के लिए, हम अपनी कक्षा को "ब्लैक बॉक्स" के रूप में प्रस्तुत करते हैं और देखते हैं कि क्या हम इस मामले में उसके व्यवहार को बदल सकते हैं।
- प्रतिस्थापन बारबरा लिस्कोव का सिद्धांत (लिस्कोव प्रतिस्थापन)
"कार्यक्रम की वस्तुओं को कार्यक्रम के गुणों को बदले बिना उनके उत्तराधिकारियों द्वारा प्रतिस्थापित किया जा सकता है"
ऐसा करने के लिए, हम जांचते हैं कि क्या हमने पूर्व शर्त को मजबूत किया है और पोस्टकंडिशन को कमजोर नहीं किया है। यदि ऐसा होता है, तो सिद्धांत का सम्मान नहीं किया जाता है।
- इंटरफ़ेस अलगाव
"कई विशिष्ट इंटरफेस एक सार्वभौमिक से बेहतर हैं।"
हम जांचते हैं कि इंटरफ़ेस में कितनी विधियाँ हैं और इन विधियों पर अलग-अलग कार्य कैसे लागू होते हैं, और यदि आवश्यक हो, तो हम इंटरफेस को तोड़ देते हैं।
- निर्भरता इनवर्शन सिद्धांत
"विवरणों पर निर्भरता का निर्माण किया जाना चाहिए, विवरण पर नहीं"
हम यह जांचते हैं कि क्या कक्षाएं कुछ अन्य वर्गों (सीधे अन्य वर्गों की वस्तुओं आदि) पर निर्भर करती हैं और अगर यह निर्भरता होती है, तो हम इसे अमूर्तता पर निर्भरता से बदल देते हैं।
मुझे उम्मीद है कि मेरी चीट शीट एसओएलआईडी के सिद्धांतों को समझने में किसी की मदद करेगी और अपनी परियोजनाओं में उनके उपयोग को प्रोत्साहन देगी।
आपका ध्यान देने के लिए धन्यवाद।
PS टिप्पणियों में एक अच्छी किताब की सलाह दी - रॉबर्ट मार्टिन "रैपिड सॉफ्टवेयर डेवलपमेंट।" वहां, SOLID सिद्धांतों को बहुत विस्तार से और उदाहरणों के साथ वर्णित किया गया है।