ชุดแอป 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
สโลแกนหลัก
แข็ง เป็นตัวย่อช่วยจำ ช่วยในการกำหนดหลักการออกแบบเชิงวัตถุพื้นฐานห้าประการ:
- ส หลักการความรับผิดชอบของ ingle
- โอ หลักการปิดปากกา
- ล หลักการทดแทน iskov
- ฉัน หลักการแยกส่วนหน้า
- ด หลักการผกผัน ependency
ต่อไปเราจะพูดถึงแต่ละรายการเป็นรายบุคคล ฉันจะยกตัวอย่างโค้ดที่ไม่ดีและดีสำหรับแต่ละรายการ ตัวอย่างเหล่านี้เขียนขึ้นสำหรับ Android โดยใช้ภาษา Kotlin
หลักการความรับผิดชอบเดียว
คลาสควรมีความรับผิดชอบเดียวเท่านั้น
แต่ละคลาสหรือโมดูลควรรับผิดชอบส่วนหนึ่งของฟังก์ชันที่แอปจัดเตรียมให้ ดังนั้นเมื่อจัดการกับสิ่งหนึ่ง ควรมีเพียงหนึ่งเหตุผลหลักในการเปลี่ยนแปลง หากชั้นเรียนหรือโมดูลของคุณทำมากกว่า 1 อย่าง คุณควรแยกฟังก์ชันการทำงานแยกกัน
เพื่อให้เข้าใจหลักการนี้ดีขึ้น ฉันจะยกตัวอย่างมีด Swiss Army มีดนี้เป็นที่รู้จักกันดีในด้านการทำงานหลายอย่างนอกเหนือจากใบมีดหลัก มีเครื่องมืออื่นๆ ในตัว เช่น ไขควง ที่เปิดกระป๋อง และอื่นๆ อีกมากมาย
คำถามธรรมดาสำหรับคุณคือเหตุใดฉันจึงแนะนำมีดเล่มนี้เป็นตัวอย่างสำหรับการใช้งานแบบเดี่ยว แต่ลองคิดดูสักครู่ คุณสมบัติหลักอื่น ๆ ของมีดนี้คือความคล่องตัวในขณะที่มีขนาดพกพา ดังนั้น แม้ว่าจะมีฟังก์ชันต่างๆ อยู่บ้าง แต่ก็ยังเหมาะกับจุดประสงค์หลักเพื่อให้มีขนาดเล็กพอที่จะพกพาติดตัวไปกับคุณได้อย่างสะดวกสบาย
กฎเดียวกันกับการเขียนโปรแกรม เมื่อคุณสร้างชั้นเรียนหรือโมดูล จะต้องมีจุดประสงค์หลักบางประการ ในเวลาเดียวกัน คุณไม่สามารถเล่นมากเกินไปได้เมื่อพยายามทำให้ทุกอย่างง่ายขึ้นโดยแยกฟังก์ชันการทำงานออก ดังนั้น จำไว้ว่า รักษาสมดุลไว้
ตัวอย่างคลาสสิกอาจเป็นวิธีที่ใช้บ่อย 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
ตัวอย่าง
เพื่อให้เข้าใจหลักการนี้ดียิ่งขึ้น ให้ฉันยกตัวอย่างคลาสสิกที่เข้าใจง่ายด้วย 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)
}
}
สรุปสั้นๆ
หากเรานึกถึงหลักการเหล่านี้ เราจะสังเกตได้ว่าหลักการเหล่านี้เป็นส่วนเสริมซึ่งกันและกัน การปฏิบัติตามหลักการ SOLID จะให้ประโยชน์มากมายแก่เรา พวกเขาจะทำให้แอปของเราใช้ซ้ำได้ บำรุงรักษาได้ ปรับขนาดได้ และทดสอบได้
แน่นอนว่าเป็นไปไม่ได้ที่จะปฏิบัติตามหลักการเหล่านี้ทั้งหมดอย่างสมบูรณ์เสมอไป เนื่องจากทุกอย่างขึ้นอยู่กับสถานการณ์แต่ละอย่างเมื่อเขียนโค้ด แต่ในฐานะนักพัฒนาซอฟต์แวร์ อย่างน้อยคุณควรรู้จักพวกเขาเพื่อที่คุณจะได้ตัดสินใจว่าจะใช้พวกเขาเมื่อใด
ที่เก็บ
นี่เป็นส่วนแรกที่เราเรียนรู้และวางแผนโครงการของเราแทนการเขียนโค้ดใหม่ นี่คือลิงค์ไปยังสาขาที่ 1 คอมมิต ซึ่งโดยพื้นฐานแล้วคือรหัสเริ่มต้นของโครงการ "สวัสดีชาวโลก"
ดูซอร์สบน GitHub
ฉันหวังว่าฉันจะสามารถอธิบายหลักการ SOLID ได้ดี โปรดแสดงความคิดเห็นด้านล่าง
อาชิอู! ขอบคุณที่อ่าน! ฉันเผยแพร่โพสต์นี้สำหรับบล็อกส่วนตัวของฉัน www.baruckis.com เมื่อวันที่ 23 กุมภาพันธ์ 2018