जेव्हा तुम्ही मोठ्या पायथॉन प्रकल्पांवर काम करायला सुरुवात करता तेव्हा तुमच्या लक्षात येणारी पहिली गोष्ट म्हणजे कोड समजणे, चाचणी करणे आणि विस्तारणे कठीण होते. जर तुम्ही काही मूलभूत डिझाइन नियमांचे पालन केले नाही तर. तिथेच प्रसिद्ध SOLID तत्त्वे येतात: टीमचे जीवन खूप सोपे करण्यासाठी डिझाइन केलेल्या सर्वोत्तम पद्धतींचा संग्रह.
या तत्त्वांचा उगम या क्षेत्रात झाला क्लासिक ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग (जावा, सी++, सी#, इ.)परंतु जर तुम्ही वर्ग आणि वस्तू कमी-अधिक गंभीर पद्धतीने वापरत असाल तर ते पायथॉनमध्ये पूर्णपणे बसतात. ते काय आहेत, ते कुठून येतात, ते का महत्त्वाचे आहेत आणि सर्वात महत्त्वाचे म्हणजे कसे यावर सविस्तरपणे विचार करूया. पायथॉनमध्ये स्पष्ट उदाहरणांसह SOLID लागू करा. तुमचा कोड अधिक देखभाल करण्यायोग्य, स्केलेबल आणि काम करण्यास आनंददायी बनवण्यासाठी.
सॉलिड म्हणजे काय आणि ते सर्व कुठून येते?
टर्म सॉलिड हे मायकेल फेदर्स यांनी लोकप्रिय केलेले एक संक्षिप्त रूप आहे. मूळतः रॉबर्ट सी. मार्टिन यांनी मांडलेल्या पाच डिझाइन तत्त्वांचे गट करणे, ज्यांना अंकल बॉब म्हणून ओळखले जाते. अॅजाइल मॅनिफेस्टोच्या स्वाक्षऱ्यांपैकी एक असलेल्या या अमेरिकन सॉफ्टवेअर अभियंत्याने ९० च्या दशकाच्या मध्यात "द प्रिन्सिपल्स ऑफ ओओडी" आणि नंतर "डिझाइन प्रिन्सिपल्स अँड डिझाईन पॅटर्न" हा लेख प्रकाशित केला, जिथे त्यांनी आधुनिक ऑब्जेक्ट-ओरिएंटेड डिझाइनचे अनेक पाया घातले.
कालांतराने, इतर लेखक जसे की बारबरा लिस्कोव्ह आणि बर्ट्रांड मेयर त्यांनी या तत्त्वांच्या संचामध्ये एकत्रित केलेल्या कल्पना देखील मांडल्या. मायकेल फेदर्सना त्यांची पुनर्रचना करण्याची (अतिशय हुशार) कल्पना होती जेणेकरून आद्याक्षरांनी SOLID हा शब्द तयार केला, ज्यामुळे ते विकास समुदायात वणव्यासारखे पसरण्यास मदत झाली.
SOLID चे पाच अक्षरे या ऑब्जेक्ट-ओरिएंटेड डिझाइन तत्त्वांशी संबंधित आहेत, जे पायथॉनला देखील लागू होतात:
- एस - एकल जबाबदारी तत्व (एकल जबाबदारीचे तत्व)
- ओ - उघडा/बंद तत्व (खुले/बंद तत्व)
- एल - लिस्कोव्ह सबस्टिट्यूशन तत्व (लिस्कोव्ह सबस्टिट्यूशन तत्व)
- I – इंटरफेस पृथक्करण तत्व (इंटरफेस पृथक्करणाचे तत्व)
- डी - अवलंबित्व उलटा तत्व (अवलंबन उलटण्याचे तत्व)
सर्वसाधारण कल्पना अशी आहे की ही पाच तत्वे एकत्रितपणे वापरली जातात, ते तुम्हाला लवचिक, चाचणी करण्यास सोपे आणि देखभाल करण्यायोग्य सॉफ्टवेअर लिहिण्यास मदत करतात.यामुळे प्रकल्प काही वर्षांपासून उत्पादनात असताना जलद तैनाती, कमी गूढ बग, चांगले कोड पुनर्वापर आणि कमी डोकेदुखी निर्माण होते.
पायथॉनमध्ये SOLID तत्त्वे कशासाठी वापरली जातात?
पायथॉनमध्ये SOLID तत्त्वे लागू करणे ही केवळ एक शैक्षणिक प्रक्रिया नाही; त्याचा संघाच्या दैनंदिन कामावर थेट परिणाम होतो. जेव्हा तुम्ही या तत्त्वांचे पालन करता, ते स्पॅगेटी कोड कमी करतात, कोडचा वास कमी करतात आणि तुमच्या कोडबेसला "कुजलेला वास येण्यापासून" रोखतात."जर त्याला वाईट वास येत असेल तर काहीतरी खराब डिझाइन केलेले आहे" या प्रसिद्ध उपमाचा वापर करून, विंडोजमध्ये, बरेच डेव्हलपर्स निवडतात WSL2 स्थापित आणि कॉन्फिगर करा उत्पादनाच्या जवळ एक Linux वातावरण असणे.
सहयोगी वातावरणात (बॅकएंड डेव्हलपमेंट टीम्स, डेटा इंजिनिअरिंग, दीर्घ चक्र असलेली उत्पादने इ.) ही तत्त्वे महत्त्वाची आहेत एकाच कोडबेसवर अनेक लोक त्यांच्या मर्यादा ओलांडल्याशिवाय किंवा अगदी थोड्याशा स्पर्शानेही सर्वकाही न तोडता काम करू शकतात.शिवाय, पायथॉन, जरी लवचिक आणि गतिमान असला तरी, विशिष्ट OOP अॅबस्ट्रॅक्शन्सच्या अखंड अनुप्रयोगास अनुमती देतो: अॅबस्ट्रॅक्ट क्लासेस, इनहेरिटन्स पदानुक्रम, रचना आणि इंटरफेस द्वारे abcइ
थोडक्यात, SOLID तुम्हाला हे साध्य करण्यास मदत करते:
- स्वच्छ, अधिक वाचनीय कोडते लिहिल्यानंतरही वर्षानुवर्षे.
- चांगली चाचणीक्षमताकारण जबाबदाऱ्या स्पष्टपणे विभागल्या आहेत.
- उच्च पुनर्वापरयोग्यता आणि स्केलेबिलिटी मॉड्यूल्समधील कमी कठोर अवलंबित्वांमुळे.
- कमी संपार्श्विक त्रुटीजेव्हा तुम्ही एका मॉड्यूलमध्ये काहीतरी बदलता तेव्हा तुम्ही चुकूनही इतर पाच गोष्टी मोडत नाही.
एस - एकल जबाबदारी तत्व
पहिले तत्व असे सांगते की वर्गाला बदलण्याचे एकच कारण असले पाहिजे.दुसऱ्या शब्दांत, त्याला एकच, सुस्पष्ट जबाबदारी स्वीकारावी लागेल. याचा अर्थ फक्त एकच पद्धत असणे असा नाही, तर त्याचे सर्व तर्क एकाच, सुसंगत उद्देशाकडे निर्देशित केले पाहिजेत.
कल्पना करा की एक पायथॉन क्लास आहे जो वापरकर्त्याचे प्रतिनिधित्व करतो आणि त्यांचा डेटा साठवण्याव्यतिरिक्त, डेटाबेसमध्ये प्रवेश करणे आणि अहवाल तयार करणे देखील हाताळतो:
class User:
def __init__(self, name: str):
self.name = name
def get_user_from_database(self, user_id: int) -> dict:
# Recupera datos desde la base de datos
# ...
pass
def save_user_to_database(self) -> None:
# Persiste el usuario en la base de datos
# ...
pass
def generate_user_report(self) -> str:
# Genera un informe del usuario
# ...
pass
हा वर्ग आहे. तीन वेगवेगळ्या जबाबदाऱ्या एकत्र करतातवापरकर्त्याचे प्रतिनिधित्व करणे, सातत्य व्यवस्थापित करणे आणि अहवाल तयार करणे. डेटाबेस, अहवाल स्वरूप किंवा वापरकर्ता गुणधर्मांमधील बदलांसाठी समान वर्गात बदल करणे आवश्यक आहे, ज्यामुळे क्रॉस-कटिंग बग्स येण्याचा धोका वाढतो.
जर आपण या समस्या वेगळ्या केल्या तर डिझाइनमध्ये लक्षणीय सुधारणा होते:
class User:
def __init__(self, name: str):
self.name = name
class UserDB:
@staticmethod
def get_user(user_id: int) -> User:
# Lógica para obtener usuarios de la base de datos
# ...
return User("John Doe")
@staticmethod
def save_user(user: User) -> None:
# Lógica para guardar el usuario
# ...
pass
class UserReportGenerator:
@staticmethod
def generate_report(user: User) -> str:
# Lógica para generar informes de usuario
# ...
return f"Report for user: {user.name}"
आता वर्ग वापरकर्ता फक्त वापरकर्त्याचे अस्तित्व म्हणून प्रतिनिधित्व करतो.जर अहवाल तयार करण्याची पद्धत बदलली तर फक्त टॅप करा UserReportGeneratorजर तुम्ही डेटाबेस बदललात तर तुम्ही फक्त स्पर्श करा UserDBप्रत्येक वर्गात बदलाचे एकच कारण असते, जे डीबगिंग आणि सिस्टम उत्क्रांती सुलभ करते.
एसआरपी अधिक वास्तववादी उदाहरणावर लागू केले: बदके आणि संवाद
चला एका क्लासिक परिस्थितीकडे पाहूया: एक वर्ग बदक सुरुवातीला, जबाबदाऱ्या हळूहळू जोडल्या जातात जोपर्यंत ते एक राक्षस बनत नाही जे राखणे कठीण असते. एका साध्या अंमलबजावणीची कल्पना करा:
class Duck:
def __init__(self, name: str):
self.name = name
def fly(self) -> None:
print(f"{self.name} is flying not very high")
def swim(self) -> None:
print(f"{self.name} swims in the lake and quacks")
def do_sound(self) -> str:
return "Quack"
def greet(self, other_duck: "Duck") -> None:
print(f"{self.name}: {self.do_sound()}, hello {other_duck.name}")
वर्ग त्याची व्याख्या फक्त "बदक" अशी करावी.पण ते एकमेकांशी कसे संवाद साधतात हे देखील व्यवस्थापित करते. जर उद्या तुम्ही संभाषणाचे तर्कशास्त्र बदलले (अधिक वाक्ये, इतर भाषा, वेगवेगळे चॅनेल), तर तुम्हाला डक क्लासमध्ये बदल करावा लागेल, जो आधीच एक अस्तित्व म्हणून चांगले कार्य करतो.
एसआरपीचा आदर करणारा उपाय म्हणजे संवादात विशेषज्ञ असलेल्या दुसऱ्या वर्गाकडून ती दुसरी जबाबदारी काढून घेणे:
class Duck:
def __init__(self, name: str):
self.name = name
def fly(self) -> None:
print(f"{self.name} is flying not very high")
def swim(self) -> None:
print(f"{self.name} swims in the lake and quacks")
def do_sound(self) -> str:
return "Quack"
class Communicator:
def __init__(self, channel: str):
self.channel = channel
def communicate(self, duck1: Duck, duck2: Duck) -> None:
sentence1 = f"{duck1.name}: {duck1.do_sound()}, hello {duck2.name}"
sentence2 = f"{duck2.name}: {duck2.do_sound()}, hello {duck1.name}"
conversation =
print(*conversation, f"(via {self.channel})", sep="\n")
या वेगळेपणाबद्दल धन्यवाद, बदकाच्या व्याख्येला स्पर्श न करता तुम्ही संवादाचे तर्कशास्त्र विकसित करू शकता.शिवाय, कोडची चाचणी करणे सोपे आहे: तुम्ही वर्तनाची चाचणी करता Duck आणि दुसरीकडे, त्यापैकी एक Communicatorजबाबदाऱ्या मिसळल्याशिवाय.
ओ - उघडा/बंद तत्व
OCP तत्व असे सांगते की सॉफ्टवेअर घटकांनी त्यांचे वर्तन वाढवण्यासाठी खुले असले पाहिजे, परंतु थेट सुधारणांसाठी बंद असले पाहिजे.दुसऱ्या शब्दांत सांगायचे तर, जेव्हा तुम्हाला नवीन कार्यक्षमता जोडायची असेल, तेव्हा आदर्शपणे तुम्हाला आधीच काम करणारे आणि इतर मॉड्यूलद्वारे वापरले जाणारे वर्ग पुन्हा लिहावे लागू नयेत.
भौमितिक आकृत्यांच्या क्षेत्रफळांची गणना करणे हे एक उत्कृष्ट उदाहरण आहे. प्रथम आपण एक आवृत्ती पाहू ज्यामध्ये ओसीपीचा आदर करत नाही.:
class Rectangle:
def __init__(self, width: float, height: float):
self.width = width
self.height = height
class Circle:
def __init__(self, radius: float):
self.radius = radius
class AreaCalculator:
def calculate_area(self, shape) -> float:
if isinstance(shape, Rectangle):
return shape.width * shape.height
elif isinstance(shape, Circle):
return 3.14159 * shape.radius * shape.radius
else:
raise ValueError("Forma no soportada")
जर तुम्हाला उद्या त्रिकोण जोडायचा असेल तर तुम्हाला सक्ती करावी लागेल चा कोड सुधारित करा AreaCalculatorआणखी एक जोडत आहे elifहे OCP चे उल्लंघन करते, कारण वर्ग आता बदलांसाठी "बंद" नाही.
योग्य आवृत्तीमध्ये एक अमूर्तता सादर करणे समाविष्ट आहे Shape एका पद्धतीने area() जे प्रत्येक आकृती त्यांच्या पद्धतीने अंमलात आणते:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius * self.radius
class AreaCalculator:
def calculate_area(self, shape: Shape) -> float:
return shape.area()
या डिझाइनबद्दल धन्यवाद, साठी स्पर्श न करणारा त्रिकोण जोडा. AreaCalculatorतुम्ही फक्त एक नवीन उपवर्ग तयार करा:
class Triangle(Shape):
def __init__(self, base: float, height: float):
self.base = base
self.height = height
def area(self) -> float:
return 0.5 * self.base * self.height
उघडा/बंद तत्व या कल्पनेशी अगदी व्यवस्थित जुळते अॅबस्ट्रॅक्शनद्वारे स्पष्ट विस्तार बिंदू परिभाषित करा.: इंटरफेस, अॅबस्ट्रॅक्ट क्लासेस, हुक इ. पायथॉनमध्ये, मॉड्यूल abc भाषा गतिमान असली तरीही, हे तुम्हाला स्पष्टपणे व्यक्त करण्याची परवानगी देते.
कम्युनिकेटरच्या उदाहरणावर OCP लागू केले
जर आपण उदाहरणाकडे परत गेलो तर कम्युनिकेटरआपण एक पाऊल पुढे जाऊन प्रत्येक वेळी कम्युनिकेटर पुन्हा न लिहिता वेगवेगळ्या प्रकारच्या संभाषणांना समर्थन देण्यासाठी डिझाइन तयार करू शकतो. हे करण्यासाठी, आपण संभाषणाचे अमूर्त रूप परिभाषित करतो आणि कम्युनिकेटरला फक्त ते वापरण्यास सांगतो:
from typing import final
from abc import ABC, abstractmethod
class AbstractConversation(ABC):
@abstractmethod
def do_conversation(self) -> list:
pass
class SimpleConversation(AbstractConversation):
def __init__(self, duck1: Duck, duck2: Duck):
self.duck1 = duck1
self.duck2 = duck2
def do_conversation(self) -> list:
sentence1 = f"{self.duck1.name}: {self.duck1.do_sound()}, hello {self.duck2.name}"
sentence2 = f"{self.duck2.name}: {self.duck2.do_sound()}, hello {self.duck1.name}"
return
class Communicator:
def __init__(self, channel: str):
self.channel = channel
@final
def communicate(self, conversation: AbstractConversation) -> None:
print(*conversation.do_conversation(), f"(via {self.channel})", sep="\n")
या आवृत्तीमध्ये, जर तुम्हाला बोलण्याची एक नवीन पद्धत जोडायची असेल तर (उदाहरणार्थ, आक्रमक संभाषण, वळण घेणारे संभाषण, इ.), तुम्ही फक्त दुसरा उपवर्ग तयार करा AbstractConversation. पद्धत communicate() de Communicator ते बदलत नाही, ओसीपीचे अक्षरशः पालन करते.
एल - लिस्कोव्ह सबस्टिट्यूशन तत्व
बारबरा लिस्कोव्ह यांनी तयार केलेले लिस्कोव्ह सबस्टिट्यूशन तत्व असे सांगते की उपवर्ग प्रोग्रामच्या अपेक्षित वर्तनात बदल न करता त्यांचे बेस क्लासेस बदलू शकतील.प्रत्यक्षात, याचा अर्थ असा की जर कोड बेस क्लासच्या एका उदाहरणासह कार्य करत असेल, तर तो उपवर्गाच्या कोणत्याही उदाहरणासह देखील कार्य करेल.
एलएसपी उल्लंघनाचे एक सामान्य उदाहरण म्हणजे सर्व पक्ष्यांचे एकाच पद्धतीने मॉडेलिंग करणे. fly()शहामृगांसह:
class Bird:
def fly(self) -> None:
pass
class Duck(Bird):
def fly(self) -> None:
print("¡El pato está volando!")
class Ostrich(Bird):
def fly(self) -> None:
# Las avestruces no vuelan
raise NotImplementedError("Las avestruces no pueden volar")
असा कोणताही कोड जो असे गृहीत धरतो की शहामृग मिळाल्यावर उडू शकणारा प्रत्येक पक्षी अपयशी ठरेल.. असे म्हणायचे आहे, Ostrich ते यासाठी वैध पर्याय नाही Bird, ज्यामुळे LSP चे उल्लंघन होते.
यावर उपाय म्हणजे वास्तवाचे चांगले प्रतिबिंब पडावे यासाठी पदानुक्रम समायोजित करणे: सर्व पक्षी उडत नाहीत, म्हणून पक्ष्यांच्या फक्त काही भागाकडेच ही पद्धत असावी fly():
class Bird:
pass
class FlyingBird(Bird):
def fly(self) -> None:
pass
class Duck(FlyingBird):
def fly(self) -> None:
print("¡El pato está volando!")
class Ostrich(Bird):
# No vuela, así que no implementa fly()
pass
या डिझाइनसह, उडणाऱ्या पक्ष्याची आवश्यकता असलेले कोणतेही कार्य घोषित करेल की त्याला उडणाऱ्या पक्ष्याची आवश्यकता आहे. FlyingBirdआणि कधीही शहामृग मिळणार नाही. अशा प्रकारे, LSP चा आदर केला जातो आणि अनपेक्षित रनटाइम अपवाद टाळले जातात.
एलएसपी आणि पक्ष्यांचे संभाषण
संभाषणांच्या उदाहरणाकडे परत येताना, फक्त बदकांबद्दल विचार करून कोडिंग सुरू करणे आणि नंतर कावळे किंवा इतर पक्षी जोडणे सामान्य आहे. जर संभाषण वर्ग यावर अवलंबून असेल तर Duck, तुम्ही ते इतर प्रकारच्या पक्ष्यांसोबत पुन्हा वापरू शकणार नाही. कोडला स्पर्श न करता:
class Crow:
# Implementación específica del cuervo
...
Si SimpleConversation हे फक्त बदकांसाठी टाइप केलेले आहे; तुम्ही त्यात बदल केल्याशिवाय त्यावर कावळा लावू शकणार नाही. योग्य दृष्टिकोन म्हणजे एक सामान्य अॅबस्ट्रॅक्शन तयार करणे. Bird आणि संभाषण त्या अमूर्ततेवर अवलंबून करा:
from abc import ABC, abstractmethod
class Bird(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def do_sound(self) -> str:
pass
class Crow(Bird):
def do_sound(self) -> str:
return "Caw"
class Duck(Bird):
def do_sound(self) -> str:
return "Quack"
class SimpleConversation(AbstractConversation):
def __init__(self, bird1: Bird, bird2: Bird):
self.bird1 = bird1
self.bird2 = bird2
def do_conversation(self) -> list:
sentence1 = f"{self.bird1.name}: {self.bird1.do_sound()}, hello {self.bird2.name}"
sentence2 = f"{self.bird2.name}: {self.bird2.do_sound()}, hello {self.bird1.name}"
return
अशाप्रकारे, कोणताही उपवर्ग Bird जे कराराचा आदर करते (do_sound()(नाव, इ.) म्हणजे एक वैध पर्यायी आणि अपेक्षित वर्तन खंडित करणार नाही SimpleConversation.
I – इंटरफेस पृथक्करण तत्व
आयएसपी तत्व असे मानते की कोणत्याही ग्राहकाला ते वापरत नसलेल्या पद्धतींवर अवलंबून राहण्याची सक्ती केली जाऊ नये.अमूर्त वर्ग किंवा इंटरफेसमध्ये अनुवादित केल्यास, याचा अर्थ असा की एका मोठ्या, सामान्य इंटरफेसपेक्षा अनेक लहान, विशिष्ट इंटरफेस असणे चांगले.
या डिझाइनचे निरीक्षण करा ज्यामध्ये इंटरफेस Worker ते अंमलात आणणाऱ्या सर्वांकडे विशिष्ट काम आणि खाण्याच्या पद्धती असणे आवश्यक आहे:
from abc import ABC, abstractmethod
class Worker(ABC):
@abstractmethod
def work(self) -> None:
pass
@abstractmethod
def eat(self) -> None:
pass
class Human(Worker):
def work(self) -> None:
print("El humano está trabajando")
def eat(self) -> None:
print("El humano está comiendo")
class Robot(Worker):
def work(self) -> None:
print("El robot está trabajando")
def eat(self) -> None:
# El robot no come, pero está obligado a declarar este método
pass
वर्ग रोबोट एका पद्धतीवर अवलंबून असतो eat() ज्याची गरज नाहीअन्नाशी संबंधित कोणताही बदल रोबोटवर परिणाम करेल, जरी त्याचा त्या वर्तनाशी काहीही संबंध नसला तरीही.
ISP लागू करून, आम्ही इंटरफेसला दोन लहान, अधिक विशिष्ट मध्ये विभागले:
class Workable(ABC):
@abstractmethod
def work(self) -> None:
pass
class Eatable(ABC):
@abstractmethod
def eat(self) -> None:
pass
class Human(Workable, Eatable):
def work(self) -> None:
print("El humano está trabajando")
def eat(self) -> None:
print("El humano está comiendo")
class Robot(Workable):
def work(self) -> None:
print("El robot está trabajando")
आता, प्रत्येक वर्ग फक्त त्याला आवश्यक असलेल्या पद्धती लागू करतो.यामुळे जोडणी कमी होते, डिझाइन उत्क्रांती सुलभ होते आणि कोड अधिक अर्थपूर्ण बनतो: कोण काय करू शकते हे अगदी स्पष्ट होते.
पक्षी मॉडेलिंगमध्ये आयएसपी: उडणे आणि पोहणे
उडणाऱ्या आणि पोहणाऱ्या पक्ष्यांचे मॉडेलिंग करतानाही असेच काहीतरी घडते. जर मूलभूत अमूर्तता Bird त्यासाठी दोन्ही अंमलात आणणे आवश्यक आहे fly() कसे swim()तुम्हाला असे वर्ग मिळतील जसे की Crow ज्यांना पोहायला येत असल्याचे भासवावे लागते:
class Bird(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def fly(self) -> None:
pass
@abstractmethod
def swim(self) -> None:
pass
@abstractmethod
def do_sound(self) -> str:
pass
आयएसपीनुसार उपाय असा आहे इंटरफेसला अधिक विशिष्ट क्षमतांमध्ये विभाजित करा.:
class Bird(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def do_sound(self) -> str:
pass
class FlyingBird(Bird):
@abstractmethod
def fly(self) -> None:
pass
class SwimmingBird(Bird):
@abstractmethod
def swim(self) -> None:
pass
class Crow(FlyingBird):
def fly(self) -> None:
print(f"{self.name} is flying high and fast!")
def do_sound(self) -> str:
return "Caw"
class Duck(SwimmingBird, FlyingBird):
def fly(self) -> None:
print(f"{self.name} is flying not very high")
def swim(self) -> None:
print(f"{self.name} swims in the lake and quacks")
def do_sound(self) -> str:
return "Quack"
जर तुम्ही कधी पेंग्विनचे मॉडेल बनवायचे ठरवले तर, फक्त तुम्ही त्याला वारसा बनवता SwimmingBird पण पासून नाही FlyingBirdआणि तुम्हाला रिकाम्या पद्धती लागू कराव्या लागणार नाहीत किंवा कृत्रिम अपवाद टाकावे लागणार नाहीत.
डी - अवलंबित्व उलटा तत्व
शेवटचे तत्व, DIP, दोन प्रमुख कल्पनांमध्ये सारांशित केले जाऊ शकते: उच्च-स्तरीय मॉड्यूल्स निम्न-स्तरीय मॉड्यूल्सवर अवलंबून नसावेत; दोन्ही अॅबस्ट्रॅक्शन्सवर अवलंबून असले पाहिजेत.आणि अमूर्तता तपशीलांवर अवलंबून नसावी, तर तपशील अमूर्ततेवर अवलंबून असले पाहिजेत.
प्रत्यक्षात, याचा अर्थ असा की तुमचा व्यवसाय तर्क "मी MySQL वापरतो," "मी स्थानिक फाइलवर लिहितो," किंवा "मी या प्रदात्यासह एसएमएस संदेश पाठवतो" अशा विशिष्ट तपशीलांशी जोडलेला नसावा. त्याऐवजी, तुम्ही परिभाषित करता अमूर्त इंटरफेस (उदाहरणार्थ, Database, Channel, NotificationService) आणि तुम्ही तुमचा उच्च-स्तरीय कोड फक्त त्यांच्याशीच बोलू देता.
अशी रचना डीआयपी तोडणे हे एक वापरकर्ता भांडार असेल जे MySQL डेटाबेस थेट स्थापित करते:
class MySQLDatabase:
def connect(self) -> None:
# Conectar a MySQL
pass
def query(self, sql: str) -> list:
# Ejecutar consulta
return []
class UserRepository:
def __init__(self) -> None:
self.database = MySQLDatabase() # Dependencia directa
def get_users(self) -> list:
return self.database.query("SELECT * FROM users")
जर तुम्ही उद्या PostgreSQL वापरायचे ठरवले तर तुम्हाला उच्च-स्तरीय वर्ग सुधारित करा UserRepositoryतुम्ही एका विशिष्ट अंमलबजावणी तपशीलाशी बांधलेले आहात.
DIP लागू करून, आपण प्रथम डेटाबेस अॅबस्ट्रॅक्शन परिभाषित करतो आणि नंतर त्यातून ठोस अंमलबजावणी वारशाने मिळवतो:
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def connect(self) -> None:
pass
@abstractmethod
def query(self, sql: str) -> list:
pass
class MySQLDatabase(Database):
def connect(self) -> None:
# Conexión a MySQL
pass
def query(self, sql: str) -> list:
# Consulta en MySQL
return []
class PostgreSQLDatabase(Database):
def connect(self) -> None:
# Conexión a PostgreSQL
pass
def query(self, sql: str) -> list:
# Consulta en PostgreSQL
return []
class UserRepository:
def __init__(self, database: Database) -> None:
self.database = database # Depende de una abstracción
def get_users(self) -> list:
return self.database.query("SELECT * FROM users")
अशा प्रकारे, तुम्ही कोणत्याही अंमलबजावणीची इंजेक्ट करू शकता Database रिपॉझिटरी तयार करताना, त्याच्या अंतर्गत कोडला स्पर्श न करता:
mysql_db = MySQLDatabase()
user_repo = UserRepository(mysql_db)
postgres_db = PostgreSQLDatabase()
user_repo = UserRepository(postgres_db)
हा नमुना म्हणून ओळखला जातो अवलंबित्व इंजेक्शन आणि DIP लागू करण्याचा हा सर्वात सामान्य मार्ग आहे: वर्ग स्वतःचे अवलंबित्व तयार करत नाहीत, परंतु ते बाहेरून (कन्स्ट्रक्टरद्वारे किंवा विशिष्ट पद्धतींद्वारे) प्राप्त करतात, नेहमी अॅबस्ट्रॅक्शन्सचा प्रकार म्हणून वापर करतात.
चॅनेल आणि कम्युनिकेटर्सना DIP लागू केला
पक्ष्यांच्या संभाषणाच्या उदाहरणात, आपण DIP लागू करून चॅनेल व्यवस्थापन देखील सुधारू शकतो. समजा तुम्ही चॅनेलसाठी एक अॅब्स्ट्रॅक्शन आणि कम्युनिकेटरसाठी दुसरे अॅब्स्ट्रॅक्शन परिभाषित केले आहे:
class AbstractChannel(ABC):
@abstractmethod
def get_channel_message(self) -> str:
pass
class AbstractCommunicator(ABC):
@abstractmethod
def get_channel(self) -> AbstractChannel:
pass
@final
def communicate(self, conversation: AbstractConversation) -> None:
print(*conversation.do_conversation(),
self.get_channel().get_channel_message(),
sep="\n")
पहिली, साधी अंमलबजावणी अशी असू शकते:
class SMSChannel(AbstractChannel):
def get_channel_message(self) -> str:
return "(via SMS)"
class SMSCommunicator(AbstractCommunicator):
def __init__(self) -> None:
self._channel = SMSChannel() # Depende de detalle concreto
def get_channel(self) -> AbstractChannel:
return self._channel
जरी ते बरोबर वाटत असले तरी, हे कम्युनिकेटर अजूनही थेट जोडलेले आहे SMSChannelआम्ही कम्युनिकेटरला बाहेरून चॅनेल प्राप्त करून (अवलंबन इंजेक्शन) डिझाइनमध्ये सुधारणा केली आणि अशा प्रकारे आम्ही केवळ अमूर्ततेवर अवलंबून राहिलो:
class SimpleCommunicator(AbstractCommunicator):
def __init__(self, channel: AbstractChannel) -> None:
self._channel = channel
def get_channel(self) -> AbstractChannel:
return self._channel
या दृष्टिकोनासह, कोणतेही नवीन चॅनेल (ईमेल, पुश सूचना इ.) लागू करते AbstractChannel y कम्युनिकेटर कोड न बदलता ते वापरता येते.पुन्हा एकदा, उच्च-स्तरीय वर्ग तपशीलांवर नव्हे तर अमूर्ततेवर अवलंबून असतात.
जेव्हा तुम्ही SOLID कडे दुर्लक्ष करता तेव्हा काय होते?
जर ही तत्वे विचारात घेतली नाहीत तर, संहितेला अशा समस्यांचा सामना करावा लागतो जसे की कोडचा वास, कोड रॉट आणि जोड्या सोडवणे अशक्यम्हणजेच, हजारो जबाबदाऱ्या असलेले मोठे वर्ग, करार मोडणारे उपवर्ग, चक्रीय अवलंबित्व आणि खूप जास्त गोष्टी करत असल्याने दररोज बदलणाऱ्या पद्धती.
त्याचे परिणाम स्पष्ट आहेत आणि कोणत्याही संघासाठी ते खूपच वेदनादायक आहेत: अधिक भेद्यता, अधिक बग, सतत रीफॅक्टरिंग आणि सर्वात वाईट परिस्थितीत, कोड जो व्यावहारिकदृष्ट्या निरुपयोगी ठरतो.यालाच सामान्यतः "स्पेगेटी कोड" म्हणतात: अनुसरण करणे कठीण, पॅचेसने भरलेले आणि महत्त्वाचे काहीतरी न तोडता वाढवणे जवळजवळ अशक्य.
सॉलिड तत्त्वे दगडात रचलेली नाहीत आणि ती सर्व कठोरपणे लागू करणे नेहमीच फायदेशीर नसते, विशेषतः जलद प्रोटोटाइपिंगमध्ये किंवा खूप लहान प्रकल्पांमध्ये. तरीही, त्यांना लक्षात ठेवा आणि पायथॉनमधील तुमच्या बहुतेक ऑब्जेक्ट-ओरिएंटेड डिझाइनमध्ये ते लागू करा. कालांतराने वाढणाऱ्या प्रकल्पात आणि थोडासा वाढताच कोसळणाऱ्या प्रकल्पात फरक पडतो.