Computer >> คอมพิวเตอร์ >  >> ระบบ >> Android

การแนะนำหลักการ SOLID

ชุดแอป Kriptofolio - ตอนที่ 1

ซอฟต์แวร์อยู่ในสถานะของการเปลี่ยนแปลงเสมอ การเปลี่ยนแปลงแต่ละครั้งอาจมีผลกระทบในทางลบต่อทั้งโครงการ ดังนั้นสิ่งสำคัญคือการป้องกันความเสียหายที่สามารถทำได้ในขณะที่ใช้การเปลี่ยนแปลงใหม่ทั้งหมด

ด้วยแอพ “Kriptofolio” (ก่อนหน้านี้คือ “My Crypto Coins”) ฉันจะสร้างรหัสใหม่ทีละขั้นตอนและฉันต้องการเริ่มทำสิ่งนั้นในทางที่ดี ฉันต้องการให้โครงการของฉันมีคุณภาพที่มั่นคง อันดับแรก เราต้องเข้าใจหลักการพื้นฐานของการสร้างซอฟต์แวร์ที่ทันสมัย พวกเขาเรียกว่าหลักการ SOLID ชื่อลวงขนาดนี้! ?

เนื้อหาซีรีส์

  • บทนำ:แผนงานในการสร้างแอป Android ที่ทันสมัยในปี 2018–2019
  • ส่วนที่ 1:การแนะนำหลักการ SOLID (คุณอยู่ที่นี่)
  • ส่วนที่ 2:วิธีเริ่มสร้างแอป Android:การสร้าง Mockups, UI และเลย์เอาต์ XML
  • ส่วนที่ 3:ทั้งหมดเกี่ยวกับสถาปัตยกรรมนั้น:สำรวจรูปแบบสถาปัตยกรรมต่างๆ และวิธีใช้งานในแอปของคุณ
  • ส่วนที่ 4:วิธีการใช้ Dependency Injection ในแอปของคุณด้วย Dagger 2
  • ส่วนที่ 5:จัดการ RESTful Web Services โดยใช้ Retrofit, OkHttp, Gson, Glide และ Coroutines

สโลแกนหลัก

แข็ง เป็นตัวย่อช่วยจำ ช่วยในการกำหนดหลักการออกแบบเชิงวัตถุพื้นฐานห้าประการ:

  1. หลักการความรับผิดชอบของ ingle
  2. โอ หลักการปิดปากกา
  3. หลักการทดแทน iskov
  4. ฉัน หลักการแยกส่วนหน้า
  5. หลักการผกผัน ependency

ต่อไปเราจะพูดถึงแต่ละรายการเป็นรายบุคคล ฉันจะยกตัวอย่างโค้ดที่ไม่ดีและดีสำหรับแต่ละรายการ ตัวอย่างเหล่านี้เขียนขึ้นสำหรับ Android โดยใช้ภาษา Kotlin

หลักการความรับผิดชอบเดียว

คลาสควรมีความรับผิดชอบเดียวเท่านั้น

แต่ละคลาสหรือโมดูลควรรับผิดชอบส่วนหนึ่งของฟังก์ชันที่แอปจัดเตรียมให้ ดังนั้นเมื่อจัดการกับสิ่งหนึ่ง ควรมีเพียงหนึ่งเหตุผลหลักในการเปลี่ยนแปลง หากชั้นเรียนหรือโมดูลของคุณทำมากกว่า 1 อย่าง คุณควรแยกฟังก์ชันการทำงานแยกกัน

เพื่อให้เข้าใจหลักการนี้ดีขึ้น ฉันจะยกตัวอย่างมีด Swiss Army มีดนี้เป็นที่รู้จักกันดีในด้านการทำงานหลายอย่างนอกเหนือจากใบมีดหลัก มีเครื่องมืออื่นๆ ในตัว เช่น ไขควง ที่เปิดกระป๋อง และอื่นๆ อีกมากมาย

คำถามธรรมดาสำหรับคุณคือเหตุใดฉันจึงแนะนำมีดเล่มนี้เป็นตัวอย่างสำหรับการใช้งานแบบเดี่ยว แต่ลองคิดดูสักครู่ คุณสมบัติหลักอื่น ๆ ของมีดนี้คือความคล่องตัวในขณะที่มีขนาดพกพา ดังนั้น แม้ว่าจะมีฟังก์ชันต่างๆ อยู่บ้าง แต่ก็ยังเหมาะกับจุดประสงค์หลักเพื่อให้มีขนาดเล็กพอที่จะพกพาติดตัวไปกับคุณได้อย่างสะดวกสบาย

กฎเดียวกันกับการเขียนโปรแกรม เมื่อคุณสร้างชั้นเรียนหรือโมดูล จะต้องมีจุดประสงค์หลักบางประการ ในเวลาเดียวกัน คุณไม่สามารถเล่นมากเกินไปได้เมื่อพยายามทำให้ทุกอย่างง่ายขึ้นโดยแยกฟังก์ชันการทำงานออก ดังนั้น จำไว้ว่า รักษาสมดุลไว้

การแนะนำหลักการ SOLID

ตัวอย่างคลาสสิกอาจเป็นวิธีที่ใช้บ่อย 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 เพื่อแสดงข้อความที่กำหนดเองประเภทต่างๆ สำหรับผู้ใช้

? ตัวอย่างรหัสที่ไม่ถูกต้อง:

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()
    }
}

หลักการเปิด-ปิดสรุปเป้าหมายของหลักการสองข้อถัดไปที่ฉันพูดถึงด้านล่าง ไปดูกันเลย

หลักการทดแทน Liskov

อ็อบเจ็กต์ในโปรแกรมควรถูกแทนที่ด้วยอินสแตนซ์ของประเภทย่อยโดยไม่เปลี่ยนแปลงความถูกต้องของโปรแกรมนั้น

หลักการนี้ตั้งชื่อตาม Barbara Liskov ซึ่งเป็นนักวิทยาศาสตร์คอมพิวเตอร์ที่ประสบความสำเร็จ แนวคิดทั่วไปของหลักการนี้คือ อ็อบเจ็กต์ควรถูกแทนที่ด้วยอินสแตนซ์ของประเภทย่อยโดยไม่เปลี่ยนแปลงพฤติกรรมของโปรแกรม

สมมติว่าในแอปของคุณคุณมี MainClass ซึ่งขึ้นอยู่กับ BaseClass ซึ่งขยาย SubClass . กล่าวโดยย่อ เพื่อให้เป็นไปตามหลักการนี้ MainClass . ของคุณ รหัสและแอปของคุณโดยทั่วไปควรทำงานได้อย่างราบรื่นโดยไม่มีปัญหาใดๆ เมื่อคุณตัดสินใจเปลี่ยน BaseClass อินสแตนซ์ไปยัง SubClass ตัวอย่าง

การแนะนำหลักการ SOLID

เพื่อให้เข้าใจหลักการนี้ดียิ่งขึ้น ให้ฉันยกตัวอย่างคลาสสิกที่เข้าใจง่ายด้วย 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 เดียวกันเสมอไป คุณต้องหาแนวทางที่แตกต่างออกไป

หลักการแยกส่วนต่อประสาน

อินเทอร์เฟซเฉพาะไคลเอ็นต์จำนวนมากดีกว่าอินเทอร์เฟซทั่วไปเพียงอินเทอร์เฟซเดียว

แม้ชื่อจะฟังดูซับซ้อน แต่หลักการนั้นค่อนข้างเข้าใจง่าย มันระบุว่าลูกค้าไม่ควรถูกบังคับให้พึ่งพาวิธีการหรือใช้อินเทอร์เฟซที่ไม่ได้ใช้ คลาสต้องได้รับการออกแบบให้มีเมธอดและแอตทริบิวต์น้อยที่สุด เมื่อสร้างอินเทอร์เฟซพยายามอย่าทำให้ใหญ่เกินไป แทนที่จะแยกออกเป็นอินเทอร์เฟซที่เล็กกว่าเพื่อให้ไคลเอ็นต์ของอินเทอร์เฟซรู้เฉพาะเกี่ยวกับวิธีการที่เกี่ยวข้องเท่านั้น

เพื่อให้ได้แนวคิดเกี่ยวกับหลักการนี้ ฉันได้สร้างตัวอย่างโค้ดที่ไม่ดีและดีขึ้นมาอีกครั้งด้วยหุ่นยนต์ผีเสื้อและหุ่นยนต์ฮิวแมนนอยด์ ?

การแนะนำหลักการ SOLID

? ตัวอย่างรหัสที่ไม่ถูกต้อง:

/**
 * 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)
    }
}

สรุปสั้นๆ

หากเรานึกถึงหลักการเหล่านี้ เราจะสังเกตได้ว่าหลักการเหล่านี้เป็นส่วนเสริมซึ่งกันและกัน การปฏิบัติตามหลักการ SOLID จะให้ประโยชน์มากมายแก่เรา พวกเขาจะทำให้แอปของเราใช้ซ้ำได้ บำรุงรักษาได้ ปรับขนาดได้ และทดสอบได้

แน่นอนว่าเป็นไปไม่ได้ที่จะปฏิบัติตามหลักการเหล่านี้ทั้งหมดอย่างสมบูรณ์เสมอไป เนื่องจากทุกอย่างขึ้นอยู่กับสถานการณ์แต่ละอย่างเมื่อเขียนโค้ด แต่ในฐานะนักพัฒนาซอฟต์แวร์ อย่างน้อยคุณควรรู้จักพวกเขาเพื่อที่คุณจะได้ตัดสินใจว่าจะใช้พวกเขาเมื่อใด

ที่เก็บ

นี่เป็นส่วนแรกที่เราเรียนรู้และวางแผนโครงการของเราแทนการเขียนโค้ดใหม่ นี่คือลิงค์ไปยังสาขาที่ 1 คอมมิต ซึ่งโดยพื้นฐานแล้วคือรหัสเริ่มต้นของโครงการ "สวัสดีชาวโลก"

ดูซอร์สบน GitHub

ฉันหวังว่าฉันจะสามารถอธิบายหลักการ SOLID ได้ดี โปรดแสดงความคิดเห็นด้านล่าง

อาชิอู! ขอบคุณที่อ่าน! ฉันเผยแพร่โพสต์นี้สำหรับบล็อกส่วนตัวของฉัน www.baruckis.com เมื่อวันที่ 23 กุมภาพันธ์ 2018