কম্পিউটার

সলিড নীতিগুলির একটি ভূমিকা

Kriptofolio অ্যাপ সিরিজ - পার্ট 1

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

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

সিরিজ বিষয়বস্তু

  • পরিচয়:2018-2019 সালে একটি আধুনিক অ্যান্ড্রয়েড অ্যাপ তৈরি করার একটি রোডম্যাপ
  • প্রথম অংশ:সলিড নীতিগুলির একটি ভূমিকা (আপনি এখানে আছেন)
  • অংশ 2:কিভাবে আপনার Android অ্যাপ তৈরি করা শুরু করবেন:Mockups, UI, এবং XML লেআউট তৈরি করা
  • ৩য় খণ্ড:সেই আর্কিটেকচার সম্বন্ধে সমস্ত কিছু:বিভিন্ন আর্কিটেকচার প্যাটার্ন অন্বেষণ করা এবং কীভাবে সেগুলি আপনার অ্যাপে ব্যবহার করবেন
  • পার্ট 4:ড্যাগার 2 এর সাথে আপনার অ্যাপে ডিপেনডেন্সি ইনজেকশন কীভাবে প্রয়োগ করবেন
  • অংশ 5:Retrofit, OkHttp, Gson, Glide এবং Coroutines ব্যবহার করে RESTful ওয়েব পরিষেবাগুলি পরিচালনা করুন

নীতির স্লোগান

সলিড একটি স্মারক সংক্ষিপ্ত রূপ। এটি পাঁচটি মৌলিক অবজেক্ট-ওরিয়েন্টেড ডিজাইন নীতি নির্ধারণ করতে সাহায্য করে:

  1. S একক দায়িত্ব নীতি
  2. কলম-বন্ধ নীতি
  3. L ইসকভ প্রতিস্থাপন নীতি
  4. আমি ইন্টারফেস সেগ্রিগেশন নীতি
  5. D ependency inversion Principle

পরবর্তী আমরা পৃথকভাবে তাদের প্রতিটি আলোচনা করতে যাচ্ছি. প্রতিটির জন্য, আমি খারাপ বনাম ভাল কোড উদাহরণ প্রদান করতে যাচ্ছি। এই উদাহরণগুলি কোটলিন ভাষা ব্যবহার করে Android এর জন্য লেখা হয়েছে৷

একক দায়িত্বের নীতি

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

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

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

এখানে আপনার জন্য স্বাভাবিক প্রশ্ন হল কেন আমি এই ছুরিটিকে একক কার্যকারিতার উদাহরণ হিসাবে প্রস্তাব করছি? কিন্তু এটা নিয়ে একবার ভাবুন। এই ছুরির অন্য প্রধান বৈশিষ্ট্য হল গতিশীলতা এবং পকেটের আকার। তাই যদিও এটি কয়েকটি ভিন্ন ফাংশন অফার করে তা এখনও এটির মূল উদ্দেশ্যের সাথে খাপ খায় যাতে এটি আপনার সাথে আরামদায়কভাবে নিয়ে যাওয়ার জন্য যথেষ্ট ছোট।

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

সলিড নীতিগুলির একটি ভূমিকা

একটি ক্লাসিক উদাহরণ হতে পারে প্রায়শই ব্যবহৃত পদ্ধতি onBindViewHolder RecyclerView উইজেট অ্যাডাপ্টার তৈরি করার সময়।

? খারাপ কোডের উদাহরণ:

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

        /**
         *  Here method violates the Single Responsibility Principle!!!
         *  Despite its main and only responsibility to be adapting a VinylRecord object
         *  to its view representation, it is also performing data formatting as well.
         *  It has multiple reasons to be changed in the future, which is wrong.
         */

        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
        
        /**
         * Instead of performing data formatting operations here, we move that responsibility to
         * other class. Actually here you see only direct call of top-level function
         * convertArrayListToString - new Kotlin language feature. However don't be mistaken,
         * because Kotlin compiler behind the scenes still is going to create a Java class, and
         * than the individual top-level functions will be converted to static methods. So single
         * responsibility for each class.
         */

        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) {

    // Imagine that we need to add new type feedback message. What would happen?
    // We would need to modify this manager class. But to follow Open Closed Principle we
    // need to write a code that can be adapted automatically to the new requirements without
    // rewriting the old classes.

    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) {

    // Again the same situation - we need to add new type feedback message. We have to write code
    // that can be adapted to new requirements without changing the old class implementation.
    // Here the solution is to focus on extending the functionality by using interfaces and it
    // follows the 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()
    }
}

খোলা-বন্ধ নীতিটি পরবর্তী দুটি নীতির লক্ষ্যগুলিকে সংক্ষিপ্ত করে যা আমি নীচে আলোচনা করছি৷ তাহলে আসুন তাদের দিকে এগিয়ে যাই।

লিসকভ প্রতিস্থাপন নীতি

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

এই নীতির নামকরণ করা হয়েছে বারবারা লিসকভ - একজন দক্ষ কম্পিউটার বিজ্ঞানীর নামে। এই নীতির সাধারণ ধারণা হল যে বস্তুগুলিকে প্রোগ্রামের আচরণ পরিবর্তন না করেই তাদের উপপ্রকারের দৃষ্টান্ত দ্বারা প্রতিস্থাপন করা উচিত।

ধরা যাক আপনার অ্যাপে 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()
        // The result of the first rectangle area is 6, which is correct as 2 x 3 = 6.

        // The Liskov Substitution Principle states that a subclass (Square) should override
        // the parent class (Rectangle) in a way that does not break functionality from a
        // consumers’s point of view. Let's see.
        val rectangleSecond: Rectangle = Square()
        // The user assumes that it is a rectangle and try to set the width and the height as usual
        rectangleSecond.width = 2
        rectangleSecond.height = 3

        textViewRectangleSecond.text = rectangleSecond.area().toString()
        // The expected result of the second rectangle should be 6 again, but instead it is 9.
        // So as you see this object oriented approach for Square extending Rectangle is wrong.
    }
}

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)

        // Here it is presented a way how to organize these Rectangle and Square classes better to
        // meet the Liskov Substitution Principle. No more unexpected result.
        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 ক্লাসে ম্যাপ করে না। আপনাকে একটি ভিন্ন পদ্ধতি খুঁজে বের করতে হবে।

ইন্টারফেস পৃথকীকরণ নীতি

অনেক ক্লায়েন্ট-নির্দিষ্ট ইন্টারফেস একটি সাধারণ-উদ্দেশ্য ইন্টারফেসের চেয়ে ভালো।

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

এই নীতির ধারণা পেতে আমি আবার প্রজাপতি এবং হিউম্যানয়েড রোবট সহ খারাপ বনাম ভাল কোড উদাহরণ তৈরি করেছি। ?

সলিড নীতিগুলির একটি ভূমিকা

? খারাপ কোডের উদাহরণ:

/**
 * Let's imagine we are creating some undefined robot. We decide to create an interface with all
 * possible functions to it.
 */
interface Robot {
    fun giveName(newName: String)
    fun reset()
    fun fly()
    fun talk()
}

/**
 * First we are creating butterfly robot which implements that interface.
 */
class ButterflyRobot : Robot {
    var name: String = ""

    override fun giveName(newName: String) {
        name = newName
    }

    override fun reset() {
        // Calls reset command for the robot. Any robot's software should be possible to reset.
        // That is reasonable and we will implement this.
        TODO("not implemented")
    }

    override fun fly() {
        // Calls fly command for the robot. This is specific functionality of our butterfly robot.
        // We will definitely implement this.
        TODO("not implemented")
    }

    override fun talk() {
        // Calls talk command for the robot.
        // WRONG!!! Our butterfly robot is not going to talk, just fly! Why we need implement this?
        // Here it is a violation of Interface Segregation Principle as we are forced to implement
        // a method that we are not going to use.
        TODO("???")
    }
}

/**
 * Next we are creating humanoid robot which should be able to do similar actions as human and it
 * also implements same interface.
 */
class HumanoidRobot : Robot {
    var name: String = ""

    override fun giveName(newName: String) {
        name = newName
    }

    override fun reset() {
        // Calls reset command for the robot. Any robot's software should be possible to reset.
        // That is reasonable and we will implement this.
        TODO("not implemented")
    }

    override fun fly() {
        // Calls fly command for the robot.
        // That the problem! We have never had any intentions for our humanoid robot to fly.
        // Here it is a violation of Interface Segregation Principle as we are forced to implement
        // a method that we are not going to use.
        TODO("???")
    }

    override fun talk() {
        // Calls talk command for the robot. This is specific functionality of our humanoid robot.
        // We will definitely implement this.
        TODO("not implemented")
    }
}

? ভালো কোডের উদাহরণ:

/**
 * Let's imagine we are creating some undefined robot. We should create a generic interface with all
 * possible functions common to all types of robots.
 */
interface Robot {
    fun giveName(newName: String)
    fun reset()
}

/**
 * Specific robots which can fly should have their own interface defined.
 */
interface Flyable {
    fun fly()
}

/**
 * Specific robots which can talk should have their own interface defined.
 */
interface Talkable {
    fun talk()
}

/**
 * First we are creating butterfly robot which implements a generic interface and a specific one.
 * As you see we are not required anymore to implement functions which are not related to our robot!
 */
class ButterflyRobot : Robot, Flyable {
    var name: String = ""

    override fun giveName(newName: String) {
        name = newName
    }

    override fun reset() {
        // Calls reset command for the robot. Any robot's software should be possible to reset.
        // That is reasonable and we will implement this.
        TODO("not implemented")
    }

    // Calls fly command for the robot. This is specific functionality of our butterfly robot.
    // We will definitely implement this.
    override fun fly() {
        TODO("not implemented")
    }
}

/**
 * Next we are creating humanoid robot which should be able to do similar actions as human and it
 * also implements generic interface and specific one for it's type.
 * As you see we are not required anymore to implement functions which are not related to our robot!
 */
class HumanoidRobot : Robot, Talkable {
    var name: String = ""

    override fun giveName(newName: String) {
        name = newName
    }

    override fun reset() {
        // Calls reset command for the robot. Any robot's software should be possible to reset.
        // That is reasonable and we will implement this.
        TODO("not implemented")
    }

    override fun talk() {
        // Calls talk command for the robot. This is specific functionality of our humanoid robot.
        // We will definitely implement this.
        TODO("not implemented")
    }
}

নির্ভরতা বিপরীত নীতি

একটি "বিমূর্তকরণের উপর নির্ভরশীল হওয়া উচিত, [নয়] কনক্রিশনের উপর।"

শেষ নীতিটি বলে যে উচ্চ-স্তরের মডিউলগুলি নিম্ন-স্তরের মডিউলগুলির উপর নির্ভর করা উচিত নয়। উভয় বিমূর্ততা উপর নির্ভর করা উচিত. বিমূর্ত বিবরণের উপর নির্ভর করা উচিত নয়। বিবরণ বিমূর্ততার উপর নির্ভর করা উচিত।

নীতির মূল ধারণা হল মডিউল এবং ক্লাসের মধ্যে সরাসরি নির্ভরতা না থাকা। পরিবর্তে তাদের বিমূর্তকরণের উপর নির্ভর করার চেষ্টা করুন (যেমন ইন্টারফেস)।

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

? খারাপ কোডের উদাহরণ:

class Radiator {
    var temperatureCelsius : Int = 0

    fun turnOnHeating(newTemperatureCelsius : Int) {
        temperatureCelsius  = newTemperatureCelsius
        // To turn on heating for the radiator we will have to do specific steps for this device.
        // Radiator will have it's own technical procedure of how it will be turned on.
        // Procedure implemented here.
        TODO("not implemented")
    }
}

class AirConditioner {
    var temperatureFahrenheit: Int = 0

    fun turnOnHeating(newTemperatureFahrenheit: Int) {
        temperatureFahrenheit = newTemperatureFahrenheit
        // To turn on heating for air conditioner we will have to do some specific steps
        // just for this device, as air conditioner will have it's own technical procedure.
        // This procedure is different compared to radiator and will be implemented here.
        TODO("not implemented")
    }
}

class SmartHome {

    // To our smart home control system we added a radiator control.
    var radiator: Radiator = Radiator()
    // But what will be if later we decide to change our radiator to air conditioner instead?
    // var airConditioner: AirConditioner = AirConditioner()
    // This SmartHome class is dependent of the class Radiator and violates Dependency Inversion Principle.

    var recommendedTemperatureCelsius : Int = 20

    fun warmUpRoom() {
        radiator.turnOnHeating(recommendedTemperatureCelsius)
        // If we decide to ignore the principle there may occur some important mistakes, like this
        // one. Here we pass recommended temperature in celsius but our air conditioner expects to
        // get it in Fahrenheit.
        // airConditioner.turnOnHeating(recommendedTemperatureCelsius)
    }
}

? ভালো কোডের উদাহরণ:

// First let's create an abstraction - interface.
interface Heating {
    fun turnOnHeating(newTemperatureCelsius : Int)
}

// Class should implement the Heating interface.
class Radiator : Heating {
    var temperatureCelsius : Int = 0

    override fun turnOnHeating(newTemperatureCelsius: Int) {
        temperatureCelsius  = newTemperatureCelsius
        // Here radiator will have it's own technical procedure implemented of how it will be turned on.
        TODO("not implemented")
    }
}

// Class should implement the Heating interface.
class AirConditioner : Heating {
    var temperatureFahrenheit: Int = 0

    override fun turnOnHeating(newTemperatureCelsius: Int) {
        temperatureFahrenheit = newTemperatureCelsius * 9/5 + 32
        // Air conditioner's turning on technical procedure will be implemented here.
        TODO("not implemented")
    }
}

class SmartHome {

    // To our smart home control system we added a radiator control.
    var radiator: Heating = Radiator()
    // Now we have an answer to the question what will be if later we decide to change our radiator
    // to air conditioner. Our class is going to depend on the interface instead of another
    // injected class.
    // var airConditioner: Heating = AirConditioner()

    var recommendedTemperatureCelsius : Int = 20

    fun warmUpRoom() {
        radiator.turnOnHeating(recommendedTemperatureCelsius)
        // As we depend on the common interface, there is no more chance for mistakes.
        // airConditioner.turnOnHeating(recommendedTemperatureCelsius)
    }
}

সংক্ষেপে যোগ করার জন্য

আমরা যদি এই সমস্ত নীতিগুলি সম্পর্কে চিন্তা করি তবে আমরা লক্ষ্য করতে পারি যে তারা একে অপরের পরিপূরক। সলিড নীতিগুলি অনুসরণ করা আমাদের অনেক সুবিধা দেবে। তারা আমাদের অ্যাপকে পুনঃব্যবহারযোগ্য, রক্ষণাবেক্ষণযোগ্য, মাপযোগ্য, পরীক্ষাযোগ্য করে তুলবে।

অবশ্যই এই সমস্ত নীতিগুলি সম্পূর্ণরূপে অনুসরণ করা সর্বদা সম্ভব নয়, কারণ কোড লেখার সময় সবকিছু পৃথক পরিস্থিতির উপর নির্ভর করে। কিন্তু একজন ডেভেলপার হিসেবে আপনার অন্তত তাদের জানা উচিত যাতে আপনি সিদ্ধান্ত নিতে পারেন কখন সেগুলি প্রয়োগ করবেন৷

ভাণ্ডার

এটি প্রথম অংশ যেখানে আমরা নতুন কোড লেখার পরিবর্তে আমাদের প্রকল্প শিখি এবং পরিকল্পনা করি। এখানে পার্ট 1 শাখার প্রতিশ্রুতির একটি লিঙ্ক রয়েছে, যা মূলত প্রকল্পের "হ্যালো ওয়ার্ল্ড" প্রাথমিক কোড।

GitHub-এ উৎস দেখুন

আমি আশা করি আমি সলিড নীতিগুলি ভালভাবে ব্যাখ্যা করতে পেরেছি। নীচে মন্তব্য করতে নির্দ্বিধায়৷

অ্যাচিউ! পড়ার জন্য ধন্যবাদ! আমি মূলত এই পোস্টটি আমার ব্যক্তিগত ব্লগ www.baruckis.com-এর জন্য 23 ফেব্রুয়ারি, 2018-এ প্রকাশ করেছি৷


  1. রুবিতে সলিড ডিজাইনের নীতি

  2. ম্যাকবুক প্রো এর জন্য সেরা 4k মনিটরের একটি ভূমিকা

  3. গুগলের প্রজেক্ট ফাই:কলিংয়ের ভবিষ্যত পরিচিতি

  4. LibreOffice 7.1 পর্যালোচনা - অনিশ্চয়তার নীতি