Kriptofolio ऐप सीरीज़ - भाग 1
सॉफ्टवेयर हमेशा बदलाव की स्थिति में होता है। प्रत्येक बदलाव पूरे प्रोजेक्ट पर नकारात्मक प्रभाव डाल सकता है। इसलिए आवश्यक चीज नए बदलावों को लागू करते समय होने वाले नुकसान को रोकना है।
"Kriptofolio" (पहले "My Crypto Coins") ऐप के साथ, मैं चरण-दर-चरण बहुत सारा नया कोड बनाऊंगा और मैं इसे अच्छे तरीके से शुरू करना चाहता हूँ। मैं चाहता हूँ कि मेरा प्रोजेक्ट ठोस गुणवत्ता वाला हो। पहले हमें आधुनिक सॉफ्टवेयर बनाने के फाउंडेशन सिद्धांतों को समझने की आवश्यकता है। उन्हें SOLID सिद्धांत कहा जाता है। कितना आकर्षक नाम है!
सीरीज़ कंटेंट
- परिचय: 2018-2019 में एक आधुनिक Android ऐप बनाने का रोडमैप
- भाग 1: SOLID सिद्धांतों का परिचय (आप यहाँ हैं)
- भाग 2: अपना Android ऐप बनाना शुरू कैसे करें: मॉकअप्स, UI, और XML लेआउट्स बनाना
- भाग 3: आर्किटेक्चर के बारे में सब कुछ: विभिन्न आर्किटेक्चर पैटर्न्स की खोज और उन्हें अपने ऐप में कैसे उपयोग करें
- भाग 4: Dagger 2 के साथ अपने ऐप में डिपेंडेंसी इंजेक्शन कैसे इम्प्लीमेंट करें
- भाग 5: Retrofit, OkHttp, Gson, Glide और Coroutines का उपयोग करके RESTful वेब सर्विसेज को हैंडल करना
सिद्धांतों का नारा
SOLID एक मेमोनिक एक्रोनिम है। यह ऑब्जेक्ट-ओरिएंटेड डिज़ाइन के पांच बुनियादी सिद्धांतों को परिभाषित करने में मदद करता है:
- Single Responsibility Principle (सिंगल रिस्पॉन्सिबिलिटी सिद्धांत)
- Open-Closed Principle (ओपन-क्लोज्ड सिद्धांत)
- Liskov Substitution Principle (लिस्कोव सब्स्टीट्यूशन सिद्धांत)
- Interface Segregation Principle (इंटरफेस सेग्रिगेशन सिद्धांत)
- Dependency Inversion Principle (डिपेंडेंसी इनवर्जन सिद्धांत)
अब हम उनमें से प्रत्येक पर व्यक्तिगत रूप से चर्चा करने जा रहे हैं। प्रत्येक के लिए, मैं खराब बनाम अच्छे कोड उदाहरण प्रदान करने जा रहा हूँ। ये उदाहरण Kotlin भाषा का उपयोग करके Android के लिए लिखे गए हैं।
सिंगल रिस्पॉन्सिबिलिटी सिद्धांत
एक क्लास की केवल एक ही जिम्मेदारी होनी चाहिए।
प्रत्येक क्लास या मॉड्यूल को ऐप द्वारा प्रदान की जाने वाली कार्यक्षमता के एक भाग के लिए जिम्मेदार होना चाहिए। इसलिए जब यह एक चीज को संभालता है, तो इसे बदलने का केवल एक मुख्य कारण होना चाहिए। यदि आपकी क्लास या मॉड्यूल एक से अधिक चीजें करता है, तो आपको कार्यक्षमताओं को अलग-अलग में विभाजित करना चाहिए।
इस सिद्धांत की बेहतर समझ के लिए, मैं एक स्विस आर्मी नाइफ को उदाहरण के रूप में लूंगा। यह चाकू अपने मुख्य ब्लेड के अलावा कई कार्यों के लिए जाना जाता है। इसके अंदर अन्य टूल्स इंटीग्रेटेड हैं, जैसे स्क्रूड्राइवर्स, कैन ओपनर, और कई अन्य।
आपके लिए यहां स्वाभाविक सवाल यह है कि मैं सिंगल फंक्शनैलिटी के लिए इस चाकू को उदाहरण के रूप में क्यों सुझा रहा हूँ? लेकिन इसके बारे में एक पल के लिए सोचें। इस चाकू की दूसरी मुख्य विशेषता पॉकेट साइज़ होने के दौरान मोबिलिटी है। इसलिए भले ही यह कुछ अलग कार्य प्रदान करता है, फिर भी यह आपके साथ आराम से ले जाने के लिए काफी छोटा होने के अपने मुख्य उद्देश्य को पूरा करता है।
प्रोग्रामिंग के साथ भी यही नियम लागू होते हैं। जब आप अपनी क्लास या मॉड्यूल बनाते हैं, तो इसका कुछ मुख्य वैश्विक उद्देश्य होना चाहिए। साथ ही आप हर चीज को बहुत अधिक सरल बनाने की कोशिश करके कार्यक्षमता को अलग करके अति नहीं कर सकते। इसलिए याद रखें, संतुलन बनाए रखें।

RecyclerView विजेट एडाप्टर बनाते समय अक्सर उपयोग की जाने वाली मेथड onBindViewHolder एक क्लासिक उदाहरण हो सकती है।
❌ खराब कोड उदाहरण:
class MusicVinylRecordRecyclerViewAdapter(private val vinyls: List<VinylRecord>, private val itemLayout: Int) : RecyclerView.Adapter<MusicVinylRecordRecyclerViewAdapter.ViewHolder>() { ... override fun onBindViewHolder(holder: ViewHolder, position: Int) { val vinyl = vinyls[position] holder.itemView.tag = vinyl holder.title!!.text = vinyl.title holder.author!!.text = vinyl.author holder.releaseYear!!.text = vinyl.releaseYear holder.country!!.text = vinyl.country holder.condition!!.text = vinyl.condition /** * यहां मेथड सिंगल रिस्पॉन्सिबिलिटी सिद्धांत का उल्लंघन करता है!!! * इसकी मुख्य और एकमात्र जिम्मेदारी VinylRecord ऑब्जेक्ट को उसके व्यू प्रतिनिधित्व के अनुकूल बनाने के बावजूद, यह डेटा फॉर्मेटिंग भी कर रहा है। * भविष्य में इसे बदलने के कई कारण हैं, जो गलत है। */ var genreStr = "" for (genre in vinyl.genres!!) { genreStr += genre + ", " } genreStr = if (genreStr.isNotEmpty()) genreStr.substring(0, genreStr.length - 2) else genreStr holder.genre!!.text = genreStr } ... }✅ अच्छा कोड उदाहरण:
class MusicVinylRecordRecyclerViewAdapter(private val vinyls: List<VinylRecord>, private val itemLayout: Int) : RecyclerView.Adapter<MusicVinylRecordRecyclerViewAdapter.ViewHolder>() { ... override fun onBindViewHolder(holder: ViewHolder, position: Int) { val vinyl = vinyls[position] holder.itemView.tag = vinyl holder.title!!.text = vinyl.title holder.author!!.text = vinyl.author holder.releaseYear!!.text = vinyl.releaseYear holder.country!!.text = vinyl.country holder.condition!!.text = vinyl.condition /** * यहां डेटा फॉर्मेटिंग ऑपरेशन करने के बजाय, हम उस जिम्मेदारी को * दूसरी क्लास पर ले जाते हैं। वास्तव में यहां आप केवल टॉप-लेवल फंक्शन * convertArrayListToString का डायरेक्ट कॉल देखते हैं - Kotlin भाषा की नई सुविधा। हालांकि गलती न करें, * क्योंकि Kotlin कंपाइलर पर्दे के पीछे अभी भी एक Java क्लास बनाने जा रहा है, और * फिर व्यक्तिगत टॉप-लेवल फंक्शन्स को स्टैटिक मेथड्स में बदल दिया जाएगा। तो प्रत्येक क्लास के लिए सिंगल रिस्पॉन्सिबिलिटी। */ holder.genre!!.text = convertArrayListToString(vinyl.genres) } ... }सिंगल रिस्पॉन्सिबिलिटी सिद्धांत को ध्यान में रखकर विशेष रूप से डिज़ाइन किया गया कोड उन अन्य सिद्धांतों के करीब होगा जिन पर हम चर्चा करने जा रहे हैं।
ओपन-क्लोज्ड सिद्धांत
सॉफ्टवेयर एंटिटीज़ एक्सटेंशन के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए बंद।
यह सिद्धांत बताता है कि जब आप क्लासेज, मॉड्यूल्स और फंक्शन्स जैसे सभी सॉफ्टवेयर पार्ट्स लिखते हैं, तो आपको उन्हें एक्सटेंशन के लिए खुला लेकिन किसी भी संशोधन के लिए बंद बनाना चाहिए। इसका क्या मतलब है?
मान लीजिए हम एक वर्किंग क्लास बनाते हैं। यदि हमें नई कार्यक्षमता जोड़ने या कुछ बदलाव करने की आवश्यकता है तो उस क्लास को ट्वीक करने की कोई आवश्यकता नहीं होनी चाहिए। इसके बजाय हमें उस क्लास को उसके नए सबक्लास बनाकर एक्सटेंड करने में सक्षम होना चाहिए जहां हम आसानी से सभी नई आवश्यक सुविधाएं जोड़ सकें। फीचर्स को हमेशा इस तरह पैरामीटराइज़ किया जाना चाहिए कि एक सबक्लास उन्हें ओवरराइड कर सके।
आइए एक उदाहरण देखें जहां हम यूज़र के लिए विभिन्न प्रकार के कस्टम मैसेज दिखाने के लिए एक विशेष FeedbackManager क्लास बनाएंगे।
❌ खराब कोड उदाहरण:
class MainActivity : AppCompatActivity() { lateinit var feedbackManager: FeedbackManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) feedbackManager = FeedbackManager(findViewById(android.R.id.content)); } override fun onStart() { super.onStart() feedbackManager.showToast(CustomToast()) } } class FeedbackManager(var view: View) { // कल्पना करें कि हमें नए प्रकार का फीडबैक मैसेज जोड़ना है। क्या होगा? // हमें इस मैनेजर क्लास को संशोधित करना होगा। लेकिन ओपन क्लोज्ड सिद्धांत का पालन करने के लिए हमें // एक कोड लिखना होगा जो पुरानी क्लासेज को रीराइट किए बिना नई आवश्यकताओं के अनुकूल हो सके। fun showToast(customToast: CustomToast) { Toast.makeText(view.context, customToast.welcomeText, customToast.welcomeDuration).show() } fun showSnackbar(customSnackbar: CustomSnackbar) { Snackbar.make(view, customSnackbar.goodbyeText, customSnackbar.goodbyeDuration).show() } } class CustomToast { var welcomeText: String = "Hello, this is toast message!" var welcomeDuration: Int = Toast.LENGTH_SHORT } class CustomSnackbar { var goodbyeText: String = "Goodbye, this is snackbar message.." var goodbyeDuration: Int = Toast.LENGTH_LONG }✅ अच्छा कोड उदाहरण:
class MainActivity : AppCompatActivity() { lateinit var feedbackManager: FeedbackManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) feedbackManager = FeedbackManager(findViewById(android.R.id.content)); } override fun onStart() { super.onStart() feedbackManager.showSpecialMessage(CustomToast()) } } class FeedbackManager(var view: View) { // फिर वही स्थिति - हमें नए प्रकार का फीडबैक मैसेज जोड़ना है। हमें कोड लिखना होगा // जो पुराने क्लास इम्प्लीमेंटेशन को बदले बिना नई आवश्यकताओं के अनुकूल हो सके। // यहां समाधान इंटरफेसेज का उपयोग करके कार्यक्षमता को एक्सटेंड करने पर ध्यान केंद्रित करना है और यह // ओपन क्लोज्ड सिद्धांत का पालन करता है। fun showSpecialMessage(message: Message) { message.showMessage(view) } } interface Message { fun showMessage(view: View) } class CustomToast: Message { var welcomeText: String = "Hello, this is toast message!" var welcomeDuration: Int = Toast.LENGTH_SHORT override fun showMessage(view: View) { Toast.makeText(view.context, welcomeText, welcomeDuration).show() } } class CustomSnackbar: Message { var goodbyeText: String = "Goodbye, this is snackbar message.." var goodbyeDuration: Int = Toast.LENGTH_LONG override fun showMessage(view: View) { Snackbar.make(view, goodbyeText, goodbyeDuration).show() } }ओपन-क्लोज्ड सिद्धांत उन अगले दो सिद्धांतों के लक्ष्यों को सारांशित करता है जिनके बारे में मैं नीचे बात करता हूँ। तो आइए उन पर आगे बढ़ते हैं।
लिस्कोव सब्स्टीट्यूशन सिद्धांत
किसी प्रोग्राम में ऑब्जेक्ट्स उनके सबटाइप्स के इंस्टेंसेस से रिप्लेसेबल होने चाहिए बिना उस प्रोग्राम की शुद्धता को बदले।
यह सिद्धांत बारबरा लिस्कोव के नाम पर रखा गया है - एक कुशल कंप्यूटर वैज्ञानिक। इस सिद्धांत का सामान्य विचार यह है कि ऑब्जेक्ट्स को उनके सबटाइप्स के इंस्टेंसेस द्वारा बिना प्रोग्राम के व्यवहार को बदले रिप्लेस किया जा सकता है।
मान लीजिए आपके ऐप में MainClass है जो BaseClass पर निर्भर करता है, जो SubClass को एक्सटेंड करता है। संक्षेप में, इस सिद्धांत का पालन करने के लिए, आपका MainClass कोड और आपका ऐप सामान्य रूप से बिना किसी समस्या के निर्बाध रूप से काम करना चाहिए जब आप BaseClass इंस्टेंस को SubClass इंस्टेंस में बदलने का फैसला करते हैं।

इस सिद्धांत को और भी बेहतर समझने के लिए, मुझे आपको Square और Rectangle इनहेरिटेंस के साथ एक क्लासिक, समझने में आसान उदाहरण देने दें।
❌ खराब कोड उदाहरण:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val rectangleFirst: Rectangle = Rectangle() rectangleFirst.width = 2 rectangleFirst.height = 3 textViewRectangleFirst.text = rectangleFirst.area().toString() // पहले रेक्टेंगल के क्षेत्र का परिणाम 6 है, जो सही है क्योंकि 2 x 3 = 6। // लिस्कोव सब्स्टीट्यूशन सिद्धांत कहता है कि एक सबक्लास (Square) को पैरेंट क्लास (Rectangle) को इस तरह ओवरराइड करना चाहिए // जो उपभोक्ता के दृष्टिकोण से कार्यक्षमता को न तोड़े। आइए देखें। val rectangleSecond: Rectangle = Square() // यूज़र मानता है कि यह एक रेक्टेंगल है और हमेशा की तरह चौड़ाई और ऊंचाई सेट करने की कोशिश करता है rectangleSecond.width = 2 rectangleSecond.height = 3 textViewRectangleSecond.text = rectangleSecond.area().toString() // दूसरे रेक्टेंगल का अपेक्षित परिणाम फिर से 6 होना चाहिए, लेकिन इसके बजाय यह 9 है। // तो जैसा कि आप देख सकते हैं Square का Rectangle को एक्सटेंड करने के लिए यह ऑब्जेक्ट-ओरिएंटेड दृष्टिकोण गलत है। } } open class Rectangle { open var width: Int = 0 open var height: Int = 0 open fun area(): Int { return width * height } } class Square : Rectangle() { override var width: Int get() = super.width set(width) { super.width = width super.height = width } override var height: Int get() = super.height set(height) { super.width = height super.height = height } }✅ अच्छा कोड उदाहरण:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // यहां इन Rectangle और Square क्लासेज को लिस्कोव सब्स्टीट्यूशन सिद्धांत को पूरा करने के लिए बेहतर तरीके से व्यवस्थित करने का एक तरीका प्रस्तुत किया गया है। कोई अप्रत्याशित परिणाम नहीं। val rectangleFirst: Shape = Rectangle(2,3) val rectangleSecond: Shape = Square(3) textViewRectangleFirst.text = rectangleFirst.area().toString() textViewRectangleSecond.text = rectangleSecond.area().toString() } } class Rectangle(var width: Int, var height: Int) : Shape() { override fun area(): Int { return width * height } } class Square(var edge: Int) : Shape() { override fun area(): Int { return edge * edge } } abstract class Shape { abstract fun area(): Int }अपनी हायरार्की लिखने से पहले हमेशा सोचें। जैसा कि आप इस उदाहरण में देख सकते हैं, वास्तविक जीवन की वस्तुएं हमेशा समान OOP क्लासेज में मैप नहीं होतीं। आपको एक अलग दृष्टिकोण खोजने की आवश्यकता है।
इंटरफेस सेग्रिगेशन सिद्धांत
एक सामान्य-उद्देश्य इंटरफेस की तुलना में कई क्लाइंट-विशिष्ट इंटरफेस बेहतर हैं।
नाम जटिल लगता है, लेकिन सिद्धांत स्वयं समझने में काफी आसान है। यह बताता है कि एक क्लाइंट को कभी भी उन मेथड्स पर निर्भर करने या एक इंटरफेस इम्प्लीमेंट करने के लिए मजबूर नहीं किया जाना चाहिए जिसका वह उपयोग नहीं करता। एक क्लास को कम से कम मेथड्स और एट्रिब्यूट्स रखने के लिए डिज़ाइन किया जाना चाहिए। इंटरफेस बनाते समय इसे बहुत बड़ा न बनाने का प्रयास करें। इसके बजाय इसे छोटे इंटरफेसेज में विभाजित करें ताकि इंटरफेस का क्लाइंट केवल उन मेथड्स के बारे में जाने जो प्रासंगिक हैं।
इस सिद्धांत का विचार प्राप्त करने के लिए मैंने फिर से बटरफ्लाई और ह्यूमनॉइड रोबोट्स के साथ खराब बनाम अच्छे कोड उदाहरण बनाए हैं।

❌ खराब कोड उदाहरण:
/** * आइए कल्पना करें कि हम कुछ अनिर्धारित रोबोट बना रहे हैं। हम इसमें * सभी संभावित फंक्शन्स के साथ एक इंटरफेस बनाने का फैसला करते हैं। */ interface Robot { fun giveName(newName: String) fun reset() fun fly() fun talk() } /** * पहले हम बटरफ्लाई रोबोट बना रहे हैं जो उस इंटरफेस को इम्प्लीमेंट करता है। */ class ButterflyRobot : Robot { var name: String = "" override fun giveName(newName: String) { name = newName } override fun reset() { // रोबोट के लिए रीसेट कमांड कॉल करता है। किसी भी रोबोट के सॉफ्टवेयर को रीसेट करना संभव होना चाहिए। // यह उचित है और हम इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } override fun fly() { // रोबोट के लिए फ्लाई कमांड कॉल करता है। यह हमारे बटरफ्लाई रोबोट की विशिष्ट कार्यक्षमता है। // हम निश्चित रूप से इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } override fun talk() { // रोबोट के लिए टॉक कमांड कॉल करता है। // गलत!!! हमारा बटरफ्लाई रोबोट बात नहीं करेगा, बस उड़ेगा! हमें इसे इम्प्लीमेंट करने की आवश्यकता क्यों है? // यहां यह इंटरफेस सेग्रिगेशन सिद्धांत का उल्लंघन है क्योंकि हमें एक मेथड इम्प्लीमेंट करने के लिए मजबूर किया जाता है // जिसका हम उपयोग नहीं करने जा रहे हैं। TODO("???") } } /** * अगला हम ह्यूमनॉइड रोबोट बना रहे हैं जो मानव के समान कार्य करने में सक्षम होना चाहिए और यह * उसी इंटरफेस को भी इम्प्लीमेंट करता है। */ class HumanoidRobot : Robot { var name: String = "" override fun giveName(newName: String) { name = newName } override fun reset() { // रोबोट के लिए रीसेट कमांड कॉल करता है। किसी भी रोबोट के सॉफ्टवेयर को रीसेट करना संभव होना चाहिए। // यह उचित है और हम इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } override fun fly() { // रोबोट के लिए फ्लाई कमांड कॉल करता है। // यह समस्या है! हमारे ह्यूमनॉइड रोबोट के उड़ने का कभी कोई इरादा नहीं था। // यहां यह इंटरफेस सेग्रिगेशन सिद्धांत का उल्लंघन है क्योंकि हमें एक मेथड इम्प्लीमेंट करने के लिए मजबूर किया जाता है // जिसका हम उपयोग नहीं करने जा रहे हैं। TODO("???") } override fun talk() { // रोबोट के लिए टॉक कमांड कॉल करता है। यह हमारे ह्यूमनॉइड रोबोट की विशिष्ट कार्यक्षमता है। // हम निश्चित रूप से इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } }✅ अच्छा कोड उदाहरण:
/** * आइए कल्पना करें कि हम कुछ अनिर्धारित रोबोट बना रहे हैं। हमें सभी के लिए सामान्य * सभी संभावित फंक्शन्स के साथ एक जेनेरिक इंटरफेस बनाना चाहिए। */ interface Robot { fun giveName(newName: String) fun reset() } /** * विशिष्ट रोबोट जो उड़ सकते हैं उनका अपना इंटरफेस परिभाषित होना चाहिए। */ interface Flyable { fun fly() } /** * विशिष्ट रोबोट जो बात कर सकते हैं उनका अपना इंटरफेस परिभाषित होना चाहिए। */ interface Talkable { fun talk() } /** * पहले हम बटरफ्लाई रोबोट बना रहे हैं जो एक जेनेरिक इंटरफेस और एक विशिष्ट को इम्प्लीमेंट करता है। * जैसा कि आप देख सकते हैं हमें अब उन फंक्शन्स को इम्प्लीमेंट करने की आवश्यकता नहीं है जो हमारे रोबोट से संबंधित नहीं हैं! */ class ButterflyRobot : Robot, Flyable { var name: String = "" override fun giveName(newName: String) { name = newName } override fun reset() { // रोबोट के लिए रीसेट कमांड कॉल करता है। किसी भी रोबोट के सॉफ्टवेयर को रीसेट करना संभव होना चाहिए। // यह उचित है और हम इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } // रोबोट के लिए फ्लाई कमांड कॉल करता है। यह हमारे बटरफ्लाई रोबोट की विशिष्ट कार्यक्षमता है। // हम निश्चित रूप से इसे इम्प्लीमेंट करेंगे। override fun fly() { TODO("not implemented") } } /** * अगला हम ह्यूमनॉइड रोबोट बना रहे हैं जो मानव के समान कार्य करने में सक्षम होना चाहिए और यह * इसके प्रकार के लिए जेनेरिक इंटरफेस और विशिष्ट को भी इम्प्लीमेंट करता है। * जैसा कि आप देख सकते हैं हमें अब उन फंक्शन्स को इम्प्लीमेंट करने की आवश्यकता नहीं है जो हमारे रोबोट से संबंधित नहीं हैं! */ class HumanoidRobot : Robot, Talkable { var name: String = "" override fun giveName(newName: String) { name = newName } override fun reset() { // रोबोट के लिए रीसेट कमांड कॉल करता है। किसी भी रोबोट के सॉफ्टवेयर को रीसेट करना संभव होना चाहिए। // यह उचित है और हम इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } override fun talk() { // रोबोट के लिए टॉक कमांड कॉल करता है। यह हमारे ह्यूमनॉइड रोबोट की विशिष्ट कार्यक्षमता है। // हम निश्चित रूप से इसे इम्प्लीमेंट करेंगे। TODO("not implemented") } }डिपेंडेंसी इनवर्जन सिद्धांत
किसी को "अमूर्तताओं पर निर्भर करना चाहिए, [नहीं] ठोस चीजों पर।"
अंतिम सिद्धांत बताता है कि हाई-लेवल मॉड्यूल्स को लो-लेवल मॉड्यूल्स पर निर्भर नहीं करना चाहिए। दोनों को अमूर्तताओं पर निर्भर करना चाहिए। अमूर्तताओं को विवरणों पर निर्भर नहीं करना चाहिए। विवरणों को अमूर्तताओं पर निर्भर करना चाहिए।
सिद्धांत का मुख्य विचार मॉड्यूल्स और क्लासेज के बीच सीधी डिपेंडेंसीज़ नहीं रखना है। उन्हें अमूर्तताओं (जैसे इंटरफेसेज) पर निर्भर बनाने का प्रयास करें।
इसे और सरल बनाने के लिए, यदि आप एक क्लास के अंदर दूसरी क्लास का उपयोग करते हैं, तो यह क्लास इंजेक्टेड क्लास पर निर्भर होगी। यह सिद्धांत के विचार का उल्लंघन करता है और आपको ऐसा नहीं करना चाहिए। आपको सभी क्लासेज को डिकपल करने का प्रयास करना चाहिए।
❌ खराब कोड उदाहरण:
class Radiator { var temperatureCelsius : Int = 0 fun turnOnHeating(newTemperatureCelsius : Int) { temperatureCelsius = newTemperatureCelsius // रेडिएटर के लिए हीटिंग चालू करने के लिए हमें इस डिवाइस के लिए विशिष्ट चरण करने होंगे। // रेडिएटर की अपनी तकनीकी प्रक्रिया होगी कि यह कैसे चालू होगा। // प्रक्रिया यहां इम्प्लीमेंटेड। TODO("not implemented") } } class AirConditioner { var temperatureFahrenheit: Int = 0 fun turnOnHeating(newTemperatureFahrenheit: Int) { temperatureFahrenheit = newTemperatureFahrenheit // एयर कंडीशनर के लिए हीटिंग चालू करने के लिए हमें कुछ विशिष्ट चरण करने होंगे // केवल इस डिवाइस के लिए, क्योंकि एयर कंडीशनर की अपनी तकनीकी प्रक्रिया होगी। // यह प्रक्रिया रेडिएटर की तुलना में अलग है और यहां इम्प्लीमेंट की जाएगी। TODO("not implemented") } } class SmartHome { // हमारे स्मार्ट होम कंट्रोल सिस्टम में हमने रेडिएटर कंट्रोल जोड़ा। var radiator: Radiator = Radiator() // लेकिन बाद में क्या होगा अगर हम अपने रेडिएटर को एयर कंडीशनर में बदलने का फैसला करते हैं? // var airConditioner: AirConditioner = AirConditioner() // यह SmartHome क्लास Radiator क्लास पर निर्भर है और डिपेंडेंसी इनवर्जन सिद्धांत का उल्लंघन करती है। var recommendedTemperatureCelsius : Int = 20 fun warmUpRoom() { radiator.turnOnHeating(recommendedTemperatureCelsius) // अगर हम सिद्धांत को नजरअंदाज करते हैं तो कुछ महत्वपूर्ण गलतियाँ हो सकती हैं, जैसे यह // एक। यहां हम सेल्सियस में अनुशंसित तापमान पास करते हैं लेकिन हमारा एयर कंडीशनर इसे फारेनहाइट में प्राप्त करने की उम्मीद करता है। // airConditioner.turnOnHeating(recommendedTemperatureCelsius) } }✅ अच्छा कोड उदाहरण:
// पहले एक अमूर्तता - इंटरफेस बनाएं। interface Heating { fun turnOnHeating(newTemperatureCelsius : Int) } // क्लास को Heating इंटरफेस इम्प्लीमेंट करना चाहिए। class Radiator : Heating { var temperatureCelsius : Int = 0 override fun turnOnHeating(newTemperatureCelsius: Int) { temperatureCelsius = newTemperatureCelsius // यहां रेडिएटर की अपनी तकनीकी प्रक्रिया इम्प्लीमेंटेड होगी कि यह कैसे चालू होगा। TODO("not implemented") } } // क्लास को Heating इंटरफेस इम्प्लीमेंट करना चाहिए। class AirConditioner : Heating { var temperatureFahrenheit: Int = 0 override fun turnOnHeating(newTemperatureCelsius: Int) { temperatureFahrenheit = newTemperatureCelsius * 9/5 + 32 // एयर कंडीशनर की चालू होने की तकनीकी प्रक्रिया यहां इम्प्लीमेंट की जाएगी। TODO("not implemented") } } class SmartHome { // हमारे स्मार्ट होम कंट्रोल सिस्टम में हमने रेडिएटर कंट्रोल जोड़ा। var radiator: Heating = Radiator() // अब हमारे पास इस सवाल का जवाब है कि बाद में क्या होगा अगर हम अपने रेडिएटर को एयर कंडीशनर में बदलने का फैसला करते हैं। हमारी क्लास दूसरी इंजेक्टेड क्लास के बजाय इंटरफेस पर निर्भर करने जा रही है। // var airConditioner: Heating = AirConditioner() var recommendedTemperatureCelsius : Int = 20 fun warmUpRoom() { radiator.turnOnHeating(recommendedTemperatureCelsius) // चूंकि हम सामान्य इंटरफेस पर निर्भर करते हैं, गलतियों की कोई संभावना नहीं है। // airConditioner.turnOnHeating(recommendedTemperatureCelsius) } }संक्षेप में सारांश
यदि हम इन सभी सिद्धांतों के बारे में सोचते हैं, तो हम देख सकते हैं कि वे एक-दूसरे के पूरक हैं। SOLID सिद्धांतों का पालन करने से हमें कई लाभ मिलेंगे। वे हमारे ऐप को पुन: प्रयोज्य, रखरखाव योग्य, स्केलेबल, टेस्टेबल बनाएंगे।
बेशक इन सभी सिद्धांतों का पूरी तरह से पालन करना हमेशा संभव नहीं होता, क्योंकि कोड लिखते समय सब कुछ व्यक्तिगत स्थितियों पर निर्भर करता है। लेकिन एक डेवलपर के रूप में आपको कम से कम उन्हें जानना चाहिए ताकि आप तय कर सकें कि उन्हें कब लागू करना है।
रिपॉजिटरी
यह पहला भाग है जहां हम नया कोड लिखने के बजाय अपने प्रोजेक्ट को सीखते और प्लान करते हैं। यहां भाग 1 ब्रांच कमिट का लिंक है, जो मूल रूप से प्रोजेक्ट का "Hello world" प्रारंभिक कोड है।
GitHub पर सोर्स देखें
मुझे उम्मीद है कि मैं SOLID सिद्धांतों को अच्छी तरह से समझाने में कामयाब रहा। नीचे कमेंट्स छोड़ने के लिए स्वतंत्र महसूस करें।
Ačiū! पढ़ने के लिए धन्यवाद! मैंने मूल रूप से यह पोस्ट अपने पर्सनल ब्लॉग www.baruckis.com पर 23 फरवरी, 2018 को प्रकाशित की थी।