কম্পিউটার টিউটোরিয়াল

SOLID প্রিন্সিপল মাস্টার করুন: সuslararası সফটওয়্যার ডিজাইনের ব্যাপক গাইড

Kriptofolio app series - Part 1

সফটওয়্যার সবসময় পরিবর্তনের অবস্থায় থাকে। প্রতিটি পরিবর্তন পুরো প্রজেক্টের ওপর নেতিবাচক প্রভাব ফেলতে পারে। তাই অতywiz 업 일은 নতুন সব পরিবর্তন ইমপ্লিমেন্ট করার সময় হতে পারে এমন ক্ষতি রোধ করা।

"Kriptofolio" (পূর্বে "My Crypto Coins") অ্যাপের সাথে, আমি ধাপে ধাপে প্রচুর নতুন কোড তৈরি করব এবং আমি সেটিকে ভালোভাবে শুরু করতে চাই। আমি আমার প্রজেক্টকে solid quality দিতে চাই। প্রথমে আমাদের মডার্ন সফটওয়্যার তৈরির ফাউন্ডেশন প্রিন্সিপলগুলো বুঝতে হবে। এদেরকে SOLID principles বলা হয়। কতটুকু ক্যাচি নাম! ?

Series content

  • Introduction: A roadmap to build a modern Android app in 2018–2019
  • Part 1: An introduction to the SOLID principles (you’re here)
  • Part 2: How to start building your Android app: creating Mockups, UI, and XML layouts
  • Part 3: All about that Architecture: exploring different architecture patterns and how to use them in your app
  • Part 4: How to implement Dependency Injection in your app with Dagger 2
  • Part 5: Handle RESTful Web Services using Retrofit, OkHttp, Gson, Glide and Coroutines

Principles slogan

SOLID হলো একটি মনেমোনিক এক্রোনিম। এটি পাঁচটি বেসিক অবজেক্ট-অরিয়েন্টেড ডিজাইন প্রিন্সিপল ডিফাইন করতে সাহায্য করে:

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

পরবর্তীতে আমরা প্রতিটি আলাদা আলাদা আলোচনা করব। প্রতিটির জন্য, আমি bad vs good কোড উদাহরণ দেব। এই উদাহরণগুলো Android-র জন্য Kotlin ল্যাঙ্গুয়েজে লেখা।

Single Responsibility Principle

এক ক্লাসের শুধু একটি একক দায়বদ্ধতা হওয়া উচিত।

প্রতিটি ক্লাস বা মডিউল অ্যাপ্লিকেশন দ্বারা প্রদত্ত ফাংশন্যালিটির একটি অংশের জন্য দায়বদ্ধ হওয়া উচিত। তাই যখন এটি একটা জিনিস হ্যান্ডেল করে, তখন এটিকে পরিবর্তন করার শুধু একটি প্রধান কারণ হওয়া উচিত। যদি আপনার ক্লাস বা মডিউল একাধিক জিনিস করে, তবে ফাংশন্যালিটিগুলো আলাদা আলাদা ক্লাসে ভাগ করা উচিত।

এই প্রিন্সিপল ভালোভাবে বুঝতে, আমি একটি Swiss Army knife উদাহরণ হিসেবে নেব। এই ছুরিটি এর মেইন ব্লেড ছাড়া এর মাল্টিপল ফাংশনের জন্য বিখ্যাত। এর ভিতরে অন্যান্য টুল ইন্টিগ্রেটেড আছে, যেমন স্ক্রুড্রাইভার, ক্যান ওপেনার, এবং অন্যান্য অনেক।

এখানে আপনার জন্য স্বাভাবিক প্রশ্ন হলো কেন আমি এই ছুরিকে single functionality-এর উদাহরণ হিসেবে সাজেস্ট করছি? কিন্তু এক মুহূর্ত চিন্তা করুন। এই ছুরির অন্য একটি মূল ফিচার হলো মোবিলিটি পকেট সাইজ থাকার সাথে। তাই যদি এটা কয়েকটি আলাদা ফাংশন অফার করে, এটিও তার মূল উদ্দেশ্য পূরণ করে যে সেটি যথেষ্ট ছোট যাতে আপনি সেটিকে आरামদায়কভাবে আপনার সাথে নিয়ে যেতে পারেন।

প্রোগ্রামিং-এ একই নিয়ম 적용 হয়। আপনি আপনার ক্লাস বা মডিউল তৈরি করলে, এর কিছু মূল গ্লোবাল উদ্দেশ্য থাকতে হবে। একই সাথে আপনি সব কিছু খুব বেশি সিম্পলিফাই করার চেষ্টা করে ফাংশন্যালিটি আলাদা করার সময় overplay করতে পারবেন না। তাই মনে রাখবেন, Балান্স রাখুন।

SOLID প্রিন্সিপল মাস্টার করুন: সuslararası সফটওয়্যার ডিজাইনের ব্যাপক গাইড

একটি ক্লাসিক উদাহরণ হতে পারেRecyclerView widget adapter বানানোর সময় প্রায়ই ব্যবহৃত onBindViewHolder মেথড।

? BAD CODE EXAMPLE:

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 /** * এখানে মেথড Single Responsibility Principle লঙ্ঘন করছে!!! * এর মূল এবং একমাত্র দায়বদ্ধতা 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 } ... }

? GOOD CODE EXAMPLE:

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 ক্লাস তৈরি করবে, এবং টপ-লেভেল ফাংশনগুলো static methods-এ কনভার্ট হবে। তাই প্রতিটি ক্লাসের single responsibility। */ holder.genre!!.text = convertArrayListToString(vinyl.genres) } ... }

Single Responsibility Principle ধরে রেখে ডিজাইন করা কোড অন্যান্য প্রিন্সিপলগুলোর কাছাকাছি হবে যেগুলো আমরা আলোচনা করব।

Open-Closed Principle

সফটওয়্যার এন্টিটি এক্সটেনশনের জন্য খোলা থাকা উচিত, কিন্তু মডিফিকেশনের জন্য বন্ধ।

এই প্রিন্সিপল বলে যে যখন আপনি সকল সফটওয়্যার পার্টস যেমন ক্লাস, মডিউল, এবং ফাংশন লিখবেন, আপনাকে সেগুলো এক্সটেনশনের জন্য খোলা রাখতে হবে কিন্তু যেকোনো মডিফিকেশনের জন্য বন্ধ। এটা কী বোঝায়?

ধরি আমরা একটি ওয়ার্কিং ক্লাস তৈরি করলাম। যদি আমাদের নতুন ফাংশন্যালিটি যোগ করতে হয় বা কিছু পরিবর্তন করতে হয়, তবে সেই ক্লাসকে tweak করার কোনো দরকার নেই। বরং আমাদের সেটি এক্সটেনড করতে পারতে হবে এর নতুন সাবক্লাস তৈরি করে যেখানে আমরা সহজে সব নতুন প্রয়োজনীয় ফিচার যোগ করতে পারব। ফিচারগুলো সবসময় প্যারামিটারাইজড হওয়া উচিত যাতে সাবক্লাস সেগুলো ওভাররাইড করতে পারে।

একটি উদাহরণ দেখা যাক যেখানে আমরা একটি স্পেশাল FeedbackManager ক্লাস তৈরি করব ইউজারের জন্য ভিন্ন টাইপের কাস্টম মেসেজ দেখানোর জন্য।

? BAD CODE EXAMPLE:

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) { // কল্পনা করুন আমাদের নতুন টাইপ ফিডব্যাক মেসেজ যোগ করতে হয়। কী হবে? // আমাদের এই ম্যানেজার ক্লাস মডিফাই করতে হবে। কিন্তু Open Closed Principle ফলো করতে হলে আমাদের এমন কোড লিখতে হবে যা নতুন রিকোয়ারমেন্টে আটোমেটিক্যালি অ্যাডাপ্ট করতে পারে পুরনো ক্লাস রিরাইট ছাড়া। 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 }

? GOOD CODE EXAMPLE:

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) { // আবার একই পরিস্থিতি - আমাদের নতুন টাইপ ফিডব্যাক মেসেজ যোগ করতে হয়। আমাদের এমন কোড লিখতে হবে যা পুরনো ক্লাস ইমপ্লিমেন্টেশন পরিবর্তন ছাড়া নতুন রিকোয়ারমেন্টে অ্যাডাপ্ট করতে পারে। // এখানে সমাধান হচ্ছে ইন্টারফেস ব্যবহার করে ফাংশন্যালিটি এক্সটেনড করার ওপর ফোকাস করা এবং এটি Open Closed Principle ফলো করে। 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() } }

Open-closed principle আমি নিচে আলোচনা করব পরবর্তী দুটি প্রিন্সিপলের লক্ষ্য সারমর্ম করে। তাই এগিয়ে যাই সেগুলোর দিকে।

Liskov Substitution Principle

এক প্রোগ্রামের অবজেক্টগুলো তাদের সাবটাইপের ইনস্ট্যান্স দিয়ে রিপ্লেসেবল হওয়া উচিত প্রোগ্রামের সঠিকতা পরিবর্তন ছাড়া।

এই প্রিন্সিপলের নামকরণ Barbara Liskov-র নামে — একজন সুপ্রতিষ্ঠিত কম্পিউটার সায়েন্টিস্ট। এই প্রিন্সিপলের সাধারণ আইডিয়া হলো অবজেক্টগুলো তাদের সাবটাইপের ইনস্ট্যান্স দিয়ে রিপ্লেসেবল হওয়া উচিত প্রোগ্রামের আচরণ পরিবর্তন ছাড়া।

ধরি আপনার অ্যাপে MainClass আছে যা BaseClass-এর ওপর নির্ভরশীল, যা SubClass-এक्सটেন্ড করে। সংক্ষেপে, এই প্রিন্সিপল ফলো করার জন্য, আপনার MainClass কোড এবং আপনার অ্যাপ সাধারণত সমস্যা ছাড়া কাজ করা উচিত যখন আপনি BaseClass ইনস্ট্যান্সকে SubClass ইনস্ট্যান্স দিয়ে চেঞ্জ করবেন।

SOLID প্রিন্সিপল মাস্টার করুন: সuslararası সফটওয়্যার ডিজাইনের ব্যাপক গাইড

এই প্রিন্সিপল আরও ভালো বুঝতে, আমি আপনাকে একটি ক্লাসিক, সহজে বোঝা উদাহরণ দেব Square এবং Rectangle ইনহেরিটেন্সের সাথে।

? BAD CODE EXAMPLE:

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() // প্রথম রেক্ট্যাঙ্গেল এরিয়ার ফলাফল ৬, যা সঠিক কারণ ২ x ৩ = ৬। // Liskov Substitution Principle বলে যে একটি সাবক্লাস (Square) প্যারেন্ট ক্লাস (Rectangle)-কে এমনভাবে ওভাররাইড করা উচিত যা কনসুমার পয়েন্ট অফ ভিউ থেকে ফাংশন্যালিটি ব্রেক না করে। চলুন দেখি। val rectangleSecond: Rectangle = Square() // ইউজার ধরে নেয় এটি একটি রেক্ট্যাঙ্গেল এবং সাধারনভাবে width এবং height সেট করার চেষ্টা করে rectangleSecond.width = 2 rectangleSecond.height = 3 textViewRectangleSecond.text = rectangleSecond.area().toString() // দ্বিতীয় রেক্ট্যাঙ্গেলের এক্সপেক্টেড ফলাফল আবার ৬ হওয়া উচিত, কিন্তু বদলে ৯ আসছে। // তাই আপনি দেখছেন Square-কে Rectangle-এ এক্সটেন্ড করার এই OOP অপ্রোচ ভুল। } } 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 } }

? GOOD CODE EXAMPLE:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // এখানে Rectangle এবং Square ক্লাসগুলোকে আরও ভালোভাবে অর্গানাইজ করার পথ দেখানো হয়েছে Liskov Substitution Principle মিট করার জন্য। আর কোনো अनএক্সপেক্টেড রেজাল্ট নেই। 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 Segregation Principle

অনেক ক্লায়েন্ট-স্পেসিফিক ইন্টারফেস এক জেনারেল-পার্পজ ইন্টারফেসের চেয়ে ভালো।

নাম ভালো জটিল লাগলেও, প্রিন্সিপলটি বোঝা বস্তুত সহজ। এটি বলে যে একটি ক্লায়েন্ট কখনো বাধ্য হওয়া উচিত না এমন মেথডের ওপর নির্ভর করার জন্য বা এমন ইন্টারফেস ইমপ্লিমেন্ট করার জন্য যা সেটি ব্যবহার করে না। একটি ক্লাস ডিজাইন করা উচিত এমনভাবে যাতে এটির সবচেয়ে কম মেথড এবং অ্যাট্রিবিউট থাকে। ইন্টারফেস তৈরি করার সময় চেষ্টা করুন এটিকে খুব বড় না করার। বরং এটিকে ছোট ছোট ইন্টারফেসে ভাগ করুন যাতে ইন্টারফেসের ক্লায়েন্ট শুধু সেই মেথড সম্পর্কে জানে যা রেলিভ্যান্ট।

এই প্রিন্সিপলের আইডিয়া পাওয়ার জন্য আমি আবার বাটারফ্লাই এবং হিউম্যানয়েড রোবটের সাথে bad vs good কোড উদাহরণ তৈরি করলাম। ?

SOLID প্রিন্সিপল মাস্টার করুন: সuslararası সফটওয়্যার ডিজাইনের ব্যাপক গাইড

? BAD CODE EXAMPLE:

/** * ধরি আমরা কিছু অনির্দিষ্ট রোবট তৈরি করছি। আমরা সেটির সাথে সব সম্ভব ফাংশন সহ একটি ইন্টারফেস তৈরি করলাম। */ 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() { // রোবটের জন্য টক কমান্ড কল। // ভুল!!! আমাদের বাটারফ্লাই রোবট কথা বলবে না, শুধু উড়বে! কেন আমাদের এটি ইমপ্লিমেন্ট করতে হবে? // এখানে Interface Segregation Principle লঙ্ঘন হচ্ছে কারণ আমরা বাধ্য হচ্ছি এমন একটি মেথড ইমপ্লিমেন্ট করার জন্য যা আমরা ব্যবহার করব না। TODO("???") } } /** * পরবর্তি আমরা হিউম্যানয়েড রোবট তৈরি করছি যা মানুষের মত অ্যাকশন করতে পারবে এবং এটি একই ইন্টারফেস ইমপ্লিমেন্ট করে। */ class HumanoidRobot : Robot { var name: String = "" override fun giveName(newName: String) { name = newName } override fun reset() { // রোবটের জন্য রিসেট কমান্ড কল। যেকোনো রোবটের সফটওয়্যার রিসেট হতে পারে। // এটি যুক্তিসঙ্গত এবং আমরা ইমপ্লিমেন্ট করব। TODO("not implemented") } override fun fly() { // রোবটের জন্য ফ্লাই কমান্ড কল। // এটিই সমস্যা! আমরা কখনো আমাদের হিউম্যানয়েড রোবটকে উড়াতে চেয়ে নি। // এখানে Interface Segregation Principle লঙ্ঘন হচ্ছে কারণ আমরা বাধ্য হচ্ছি এমন একটি মেথড ইমপ্লিমেন্ট করার জন্য যা আমরা ব্যবহার করব না। TODO("???") } override fun talk() { // রোবটের জন্য টক কমান্ড কল। এটি আমাদের হিউম্যানয়েড রোবটের স্পেসিফিক ফাংশন্যালিটি। // আমরা অবশ্যই ইমপ্লিমেন্ট করব। TODO("not implemented") } }

? GOOD CODE EXAMPLE:

/** * ধরি আমরা কিছু অনির্দিষ্ট রোবট তৈরি করছি। আমাদের সব টাইপের রোবটের জন্য কমন সব সম্ভব ফাংশন সহ একটি জেনেরিক ইন্টারফেস তৈরি করা উচিত। */ 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") } }

Dependency Inversion Principle

একজনকে "abstractions-এর ওপর নির্ভর করা উচিত, [না] concretions-এ।"

শেষ প্রিন্সিপল বলে যে হাই-লেভেল মডিউলগুলো লো-লেভেল মডিউলগুলোর ওপর নির্ভরশীল হওয়া উচিত নয়। উভয় abstraction-এর ওপর নির্ভরশীল হওয়া উচিত। Abstraction ডিটেইলসের ওপর নির্ভরশীল হওয়া উচিত নয়। ডিটেইলস abstraction-এর ওপর নির্ভরশীল হওয়া উচিত।

প্রিন্সিপলের মূল আইডিয়া হলো মডিউল এবং ক্লাসের মধ্যে ডায়রেক্ট dependency না থাকা। চেষ্টা করুন সেগুলো abstractions (যেমন ইন্টারফেস) এর ওপর dependent করতে।

এটাকে আরও সিম্পল করতে গেলে, যদি আপনি একটি ক্লাস অন্য ক্লাসের ভিতরে ব্যবহার করেন, তবে এই ক্লাস ইনজেক্টেড ক্লাসের ওপর dependent হবে। এটি প্রিন্সিপলের আইডিয়া লঙ্ঘন করে এবং আপনাকে সেটি করা উচিত নয়। আপনাকে সব ক্লাস ডিকাপল করার চেষ্টা करनी উচিত।

? BAD CODE EXAMPLE:

class Radiator { var temperatureCelsius : Int = 0 fun turnOnHeating(newTemperatureCelsius : Int) { temperatureCelsius = newTemperatureCelsius // রেডিয়েটরের হিটিং অন করার জন্য আমাদের এই ডিভাইসের জন্য স্পেসifik স্টেপ করতে হবে। // রেডিয়েটরের নিজস্ব টেকনিক্যাল প্রসিডিউর থাকবে কীভাবে এটি অন হবে। // প্রসিডিউর এখানে ইমপ্লিমেন্টেড। 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 ক্লাসের ওপর dependent এবং Dependency Inversion Principle লঙ্ঘন করে। var recommendedTemperatureCelsius : Int = 20 fun warmUpRoom() { radiator.turnOnHeating(recommendedTemperatureCelsius) // যদি আমরা প্রিন্সিপল অগ্রাহ্য করি তবে কিছু গুরুত্বপূর্ণ ভুল ঘটতে পারে, এই মতো। // এখানে আমরা রিকমেন্ডেড টেম্পারेचার সেলসিয়াসে পাস করছি কিন্তু আমাদের এয়ার কন্ডিশনার ফারেনহাইটে এক্সপেক্ট করে। // airConditioner.turnOnHeating(recommendedTemperatureCelsius) } }

? GOOD CODE EXAMPLE:

// প্রথমে একটি abstraction - ইন্টারফেস তৈরি করি। 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() // এখন আমাদের কাছে উত্তর আছে যে পরবর্তীতে যদি আমরা আমাদের রেডিয়েটরকে এয়ার কন্ডিশনার দিয়ে চেঞ্জ করি। আমাদের ক্লাস ইন্টারফেসের ওপর dependent হবে অন্য ইনজেক্টেড ক্লাসের বদলে। // var airConditioner: Heating = AirConditioner() var recommendedTemperatureCelsius : Int = 20 fun warmUpRoom() { radiator.turnOnHeating(recommendedTemperatureCelsius) // যেহেতু আমরা কমন ইন্টারফেসের ওপর নির্ভর করছি, আর কোনো ভুলের সম্ভাবনা নেই। // airConditioner.turnOnHeating(recommendedTemperatureCelsius) } }

সংক্ষেপে সারমর্ম

আমরা যদি এই সব প্রিন্সিপল সম্পর্কে চিন্তা করি, তবে লক্ষ্য করব এগুলো একে অপরের পূরক। SOLID প্রিন্সিপল ফলো করলে আমাদের অনেক বেনিফিট হবে। এগুলো আমাদের অ্যাপকে reusable, maintainable, scalable, testable করবে।

অবশ্য সবসময় সব প্রিন্সিপল পুরোপুরি ফলো করা সম্ভব নয়, কারণ সব কিছু কোড লিখার সময় ব্যক্তিগত পরিস্থিতির ওপর নির্ভর করে। কিন্তু একটি ডেভেলপার হিসেবে আপনাকে কমপক্ষে সেগুলো জানা উচিত যাতে আপনি ডিসাইড করতে পারেন কখন সেগুলো অ্যাপ্লাই করবেন।

Repository

এটি প্রথম পার্ট যেখানে আমরা শিখি এবং প্ল্যান করি আমাদের প্রজেক্ট বরং নতুন কোড লিখার বদলে। এখানে পার্ট ১ ব্রাঞ্চ কমিটের লিংক, যা বেসিক্যালি প্রজেক্টের "Hello world" ইনিশিয়াল কোড।

View Source On GitHub

আশা করি আমি SOLID প্রিন্সিপল ভালোভাবে ব্যাখ্যা করতে পেরেছি। নিচে কমেন্টে আপনার মতামত জানাতে পারেন।

Ačiū! Thanks for reading! I originally published this post for my personal blog www.baruckis.com on February 23, 2018.


  1. রোবাস্ট রুবি অ্যাপ্লিকেশনের জন্য SOLID ডিজাইন প্রিন্সিপলস দক্ষতা অর্জন

  2. আপনার MacBook Pro-এর জন্য আদর্শ 4K মনিটর বেছে নিন

  3. Google Project Fi: স্মুথ মোবাইল কলিংয়ের ভবিষ্যৎ

  4. LibreOffice 7.1 রিভিউ: আপডেটেড ফিচার, পারফরম্যান্স এবং ইউজার এক্সপেরিয়েন্স