SOLID: หลักการ, 5 กฎของ OOP และการนำไปใช้ในการพัฒนา

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-05-11 เวลาอ่าน: 10 นาที

SOLID — ห้าหลักการของการเขียนโปรแกรมเชิงวัตถุที่กำหนดโดย Robert C. Martin (Uncle Bob) ในช่วงต้นปี 2000 ตามข้อมูลจาก DigitalOcean, 2024, SOLID ย่อมาจาก Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation และ Dependency Inversion หลักการเหล่านี้เป็นรากฐานของ Clean Architecture และถูกนำไปใช้ในการพัฒนา Android (MVP, MVVM, Clean Architecture) และ iOS (VIPER, TCA)

ข้อสรุปสำคัญ

  • SOLID — ตัวย่อของห้าหลักการ OOP: SRP, OCP, LSP, ISP, DIP ที่กำหนดโดย Robert C. Martin เพื่อสร้างโค้ดที่ยืดหยุ่นและบำรุงรักษาง่าย
  • SRP (Single Responsibility) — แต่ละคลาสมีเหตุผลในการเปลี่ยนแปลงหนึ่งอย่าง, หนึ่งความรับผิดชอบต่อโมดูล
  • OCP (Open-Closed) — คลาสเปิดสำหรับการขยาย แต่ปิดสำหรับการแก้ไข, นำไปใช้โดยการสืบทอดและพอลิมอร์ฟิซึม
  • LSP (Liskov Substitution) — อ็อบเจกต์ของคลาสย่อยควรแทนที่อ็อบเจกต์ของคลาสฐานโดยไม่เปลี่ยนแปลงความถูกต้องของโปรแกรม
  • ISP (Interface Segregation) — ไคลเอนต์ไม่ควรขึ้นอยู่กับอินเทอร์เฟสที่ไม่ได้ใช้, อินเทอร์เฟสควรแคบและเฉพาะเจาะจง
  • DIP (Dependency Inversion) — โมดูลระดับสูงไม่ขึ้นอยู่กับโมดูลระดับต่ำ, ทั้งสองขึ้นอยู่กับสิ่งที่เป็นนามธรรม

SOLID คืออะไร? ภาพรวมของห้าหลักการ

SOLID — ตัวย่อช่วยจำที่แสดงถึงห้าหลักการของการออกแบบเชิงวัตถุ คำนี้ถูกนำเสนอโดย Robert C. Martin ในบทความ «Design Principles and Design Patterns» (2000) และต่อมาเป็นที่นิยมในหนังสือ «Agile Software Development: Principles, Patterns, and Practices» (2002) SOLID ไม่ใช่เฟรมเวิร์กหรือไลบรารี — มันคือชุดของแนวปฏิบัติที่ทำให้โค้ดมีการเชื่อมโยงน้อยลง, ทดสอบได้ง่ายขึ้น และเปลี่ยนแปลงได้สะดวกขึ้น

ตามข้อมูลจาก Clean Coder Blog, 2014, แต่ละหลักการ SOLID แก้ปัญหาการออกแบบเฉพาะ: SRP ต่อสู้กับคลาส God, OCP ป้องกันการเปลี่ยนแปลงแบบลูกโซ่, LSP ป้องกันการสืบทอดที่ไม่ถูกต้อง, ISP หลีกเลี่ยงอินเทอร์เฟสขนาดใหญ่ และ DIP ลดการเชื่อมโยงที่แน่นหนา ร่วมกันพวกมันสร้างรากฐานของ Clean Architecture ซึ่งใช้ในโครงการ Android กับ MVP, MVVM และ MVI

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

Single Responsibility Principle (SRP) — หลักการความรับผิดชอบเดียว สูตร: «คลาสควรมีเหตุผลในการเปลี่ยนแปลงเพียงอย่างเดียว» ซึ่งหมายความว่าแต่ละโมดูลหรือคลาสรับผิดชอบอย่างแม่นยำสำหรับหนึ่งฟังก์ชันหรือหนึ่งเอนทิตีของโดเมน ถ้าคลาสจัดการทั้งผู้ใช้และการส่งอีเมล — มันมีเหตุผลในการเปลี่ยนแปลงสองอย่าง ซึ่งละเมิด SRP

ตามข้อมูลจาก Robert C. Martin, 2002, SRP เป็นหลักการที่สำคัญที่สุดและในเวลาเดียวกันถูกละเมิดมากที่สุด ในการพัฒนามือถือ SRP มักถูกละเมิดใน Activity/Fragment โดยการรวมลอจิก UI, การนำทาง, เครือข่าย และลอจิกทางธุรกิจ วิธีแก้คือแยกแต่ละชั้นเป็นคลาสแยกต่างหาก: ViewModel สำหรับลอจิก UI, Repository สำหรับข้อมูล, NavController สำหรับการนำทาง

ตัวอย่าง SRP: การแยก UserManager

พิจารณาคลาส UserManager ที่โหลดโปรไฟล์, บันทึกการตั้งค่า และส่งอีเมล นี่คือสามความรับผิดชอบที่แตกต่างกัน ซึ่งแต่ละอย่างควรถูกแยกเป็นคลาสแยกต่างหาก: UserProfileRepository (โหลด), UserSettingsStorage (บันทึก) และ EmailService (ส่ง) โค้ดไคลเอนต์ (ViewModel) ใช้ทั้งสามผ่าน Dependency Injection และแต่ละคลาสสามารถทดสอบแยกกันได้ง่ายและเปลี่ยนแปลงได้โดยไม่กระทบต่อคลาสอื่น

kotlin
// ❌ การละเมิด SRP: Activity รู้เกี่ยวกับเครือข่าย, DB และ UI
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // การเรียกเครือข่าย
        db.saveUser()    // การดำเนินการ DB
        updateUI()         // การอัปเดต UI
    }
}

// ✅ SRP ปฏิบัติตาม: ชั้นต่าง ๆ ถูกแยกแล้ว
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

สัญญาณของการละเมิด SRP: คลาสที่มีมากกว่า 200 บรรทัด, มีเมธอดจากโดเมนที่แตกต่างกัน, เปลี่ยนแปลงบ่อยด้วยเหตุผลที่แตกต่างกัน สำหรับการพัฒนา Android กฎง่าย: Activity จัดการเฉพาะวงจรชีวิตของหน้าจอ, ViewModel จัดการสถานะ UI, Repository จัดการแหล่งข้อมูล

SRP และสถาปัตยกรรมไมโครเซอร์วิส

หลักการ SRP ไม่เพียงแต่ใช้กับคลาสเท่านั้น แต่ยังใช้กับสถาปัตยกรรมระดับบริการด้วย แต่ละไมโครเซอร์วิสจัดการหนึ่งเอนทิตีโดเมน: UserService — ผู้ใช้เท่านั้น, PaymentService — การชำระเงินเท่านั้น, NotificationService — การแจ้งเตือนเท่านั้น ซึ่งช่วยให้สามารถปรับขนาด, ปรับใช้ และทดสอบบริการได้อย่างอิสระ ในแอปพลิเคชันมือถือ SRP ในระดับไมโครเซอร์วิสแสดงออกในการแยกไคลเอนต์ API ตามโดเมน

OCP: หลักการเปิด/ปิด

Open-Closed Principle (OCP) — คลาสควรเปิดสำหรับการขยาย (สามารถเพิ่มพฤติกรรมใหม่) และปิดสำหรับการแก้ไข (โค้ดที่มีอยู่ไม่ถูกเปลี่ยนแปลง) ทำได้ผ่านพอลิมอร์ฟิซึม, คลาสนามธรรม และอินเทอร์เฟส แทนที่จะเพิ่ม if-else ในเมธอดที่มีอยู่, จะสร้างการนำไปใช้อินเทอร์เฟสใหม่

ตามข้อมูลจาก Clean Coder Blog, 2014, OCP ทำงานได้ดีที่สุดกับแพทเทิร์น Strategy ตัวอย่างเช่น ถ้าแอปรองรับวิธีการชำระเงินที่แตกต่างกัน (Google Pay, Apple Pay, PayPal) ไม่จำเป็นต้องเพิ่ม switch-case ให้กับตัวประมวลผลการชำระเงิน แต่ละวิธีการชำระเงินนำไปใช้อินเทอร์เฟสร่วม PaymentGateway และระบบการชำระเงินใหม่จะถูกเพิ่มเป็นคลาสใหม่โดยไม่ต้องแก้ไขคลาสที่มีอยู่

kotlin
// ✅ OCP: เปิดสำหรับการขยาย, ปิดสำหรับการแก้ไข
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

class GooglePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

// ระบบการชำระเงินใหม่ — โดยไม่เปลี่ยนโค้ดที่มีอยู่
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP: หลักการแทนที่ของ Liskov

Liskov Substitution Principle (LSP) — หลักการแทนที่ของ Barbara Liskov ถ้า S เป็นชนิดย่อยของ T, อ็อบเจกต์ของชนิด T สามารถถูกแทนที่ด้วยอ็อบเจกต์ของชนิด S โดยไม่เปลี่ยนแปลงคุณสมบัติของโปรแกรม อย่างเป็นทางการ: ฟังก์ชันที่ใช้คลาสฐานควรทำงานได้อย่างถูกต้องกับคลาสย่อยใด ๆ ถ้าคลาสย่อยโยนข้อยกเว้นในที่ที่คลาสฐานไม่โยน — LSP ถูกละเมิด

ตามข้อมูลจาก Robert C. Martin, 2002, LSP เป็นหลักการ SOLID ที่เข้าใจยากที่สุด ตัวอย่างการละเมิดคลาสสิกคือคลาส Square ที่สืบทอดจาก Rectangle ถ้า setWidth ใน Square ตั้งทั้งความกว้างและความสูง, โค้ดไคลเอนต์ที่คาดหวังพฤติกรรมของ Rectangle จะได้รับผลลัพธ์ที่ไม่คาดคิด ในการพัฒนามือถือ LSP มักถูกละเมิดเมื่อสืบทอด ViewModel — เมื่อ ViewModel ลูกเพิ่มการพึ่งพาที่จำเป็น

kotlin
// ❌ การละเมิด LSP: Square ทำลายพฤติกรรมของ Rectangle
open class Rectangle(open var width: Int, open var height: Int)

class Square(side: Int) : Rectangle(side, side) {
    override var width
        get() = super.width
        set(value) { super.setBoth(value, value) }
}

ISP: หลักการแยกอินเทอร์เฟส

Interface Segregation Principle (ISP) — ไคลเอนต์ไม่ควรขึ้นอยู่กับอินเทอร์เฟสที่ไม่ได้ใช้ แทนที่จะมีอินเทอร์เฟสใหญ่เดียว, ให้สร้างอินเทอร์เฟสแคบและเฉพาะทางหลายอัน ถ้าคลาสนำไปใช้อินเทอร์เฟสแต่บางเมธอดโยน UnsupportedOperationException หรือว่างเปล่า — นี่คือสัญญาณชัดเจนของการละเมิด ISP

ตามข้อมูลจาก DigitalOcean, 2024, ISP มีความสำคัญโดยเฉพาะในการพัฒนามือถือเมื่อออกแบบ ViewModel และ Repository แทนที่จะมีอินเทอร์เฟส UserRepository เดียวกับเมธอด CRUD ทั้งหมด, ควรสร้าง QueryUserRepository (อ่านอย่างเดียว) และ CommandUserRepository (เขียน) แล้วไคลเอนต์ที่อ่านอย่างเดียว (องค์ประกอบ UI) จะขึ้นอยู่กับอินเทอร์เฟส Query เท่านั้นและไม่รู้เกี่ยวกับเมธอดเขียน

kotlin
// ❌ อินเทอร์เฟสใหญ่ — ไคลเอนต์ถูกบังคับให้นำเมธอดที่ไม่จำเป็นไปใช้
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP: อินเทอร์เฟสที่ถูกแยกแล้ว
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP: หลักการกลับด้านการพึ่งพา

Dependency Inversion Principle (DIP) — โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองควรขึ้นอยู่กับสิ่งที่เป็นนามธรรม (อินเทอร์เฟส) สิ่งที่เป็นนามธรรมไม่ควรขึ้นอยู่กับรายละเอียด — รายละเอียดควรขึ้นอยู่กับสิ่งที่เป็นนามธรรม นี่ไม่ใช่ «Dependency Injection» (DI) แม้ว่า DI จะเป็นวิธีทั่วไปในการนำ DIP ไปใช้

ตามข้อมูลจาก Robert C. Martin, 2019, DIP เป็นรากฐานของ Clean Architecture ViewModel (ระดับสูง) ไม่ควรสร้างอินสแตนซ์ RetrofitApi (รายละเอียด) โดยตรง แทนที่ ViewModel จะขึ้นอยู่กับอินเทอร์เฟส UserRepository และ UserRepositoryImpl ที่เป็นรูปธรรมกับ Retrofit จะถูกส่งผ่านคอนสตรักเตอร์ ใน Android DIP ถูกนำไปใช้ผ่าน Hilt/Dagger หรือ Koin: การพึ่งพาทั้งหมดถูกจัดให้ผ่านคอนเทนเนอร์ DI

kotlin
// ✅ DIP: Module ขึ้นอยู่กับสิ่งที่เป็นนามธรรม, ไม่ใช่รายละเอียด
class UserRepositoryImpl(
    private val api: UserApi,   // ขึ้นอยู่กับอินเทอร์เฟส
    private val db: UserDao     // ขึ้นอยู่กับอินเทอร์เฟส
) : UserRepository {

    override suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// Hilt DI: รายละเอียดถูกเชื่อมต่อผ่านโมดูล DI
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

การนำ SOLID ไปใช้ในการพัฒนามือถือ

SOLID ในการพัฒนามือถือ ถูกนำไปใช้ในทุกระดับ: ตั้งแต่สถาปัตยกรรมแอปพลิเคชันจนถึงคลาสแต่ละคลาส ในโครงการ Android Clean Architecture แบ่งโค้ดเป็นสามชั้น: domain (ลอจิกทางธุรกิจ — ไม่ขึ้นอยู่กับเฟรมเวิร์ก), data (คลังข้อมูล, API, DB) และ presentation (UI, ViewModel) ชั้น domain ใช้หลักการ SOLID: use case (SRP), อินเทอร์เฟสของคลังข้อมูล (DIP), คลาสเอนทิตี (OCP + LSP)

ตามข้อมูลจาก Android Developers Guide, 2025, SRP ใน Android แสดงออกในการแยก ViewModel, Repository และ Mapper OCP — เมื่อเพิ่มแหล่งข้อมูลใหม่ผ่านอินเทอร์เฟส DataSource LSP — ในการจัดการ Result แบบเดียวกันข้ามคลังข้อมูลที่แตกต่างกัน ISP — ในแนวทาง CQRS (การแยกคลังข้อมูลอ่าน/เขียน) DIP — ผ่าน Hilt/Koin สำหรับการฉีดการพึ่งพา

หลักการปัญหาที่ไม่มีมันแนวทางแก้ในโครงการมือถือ
SRPActivity ที่มี 1000+ บรรทัดViewModel + UseCase + Repository
OCPswitch-case ตามประเภทการชำระเงินStrategy: อินเทอร์เฟส PaymentGateway
LSPบั๊กเมื่อแทนที่ BaseViewModelตรวจสอบสัญญาของคลาสย่อย
ISPUnsupportedOperationExceptionแยก Reader / Writer
DIPViewModel สร้าง Retrofit ด้วยตนเองHilt / Koin DI คอนเทนเนอร์

ข้อผิดพลาดที่พบบ่อยเมื่อนำ SOLID ไปใช้

ข้อผิดพลาด SOLID ส่วนใหญ่เกี่ยวข้องกับการทำให้โค้ดซับซ้อนเกินไป อันแรก — การปฏิบัติตามหลักการอย่างเคร่งครัดโดยไม่พิจารณาบริบท การแยกคลาส UserService หนึ่งเป็น 10 อินเทอร์เฟสและ 15 คลาสเพียงเพื่อให้ได้ ISP ที่สะอาด — เป็นการออกแบบที่เกินความจำเป็น SOLID เป็นเครื่องมือ ไม่ใช่เป้าหมาย ข้อผิดพลาดที่สอง — การสับสนระหว่าง SRP กับ «หนึ่งเมธอด = หนึ่งความรับผิดชอบ» คลาสสามารถมีหลายเมธอดได้ถ้าทั้งหมดอยู่ในพื้นที่ความรับผิดชอบเดียวกัน

ตามข้อมูลจาก Simple Thread, 2024, ข้อผิดพลาดที่สาม — การละเลย LSP เมื่อสืบทอด ViewModel ใน Android ถ้า ViewModel ฐานคาดหวัง LiveData แต่ ViewModel ลูกใช้ StateFlow — โค้ดไคลเอนต์ที่สมัครรับ LiveData จะไม่ได้รับการอัปเดต ข้อผิดพลาดที่สี่ — การละเมิด DIP เพื่อการทดสอบ: RepositoryImpl สร้างอินสแตนซ์ OkHttpClient โดยตรง ทำให้การทดสอบหน่วยเป็นไปไม่ได้

กฏทองคำ: ใช้ SOLID เมื่อมันแก้ปัญหาจริง (การเปลี่ยนแปลงบ่อย, ความยากในการทดสอบ, การทำซ้ำ) สำหรับหน้าจอ CRUD ธรรมดา การปฏิบัติตามทั้งห้าหลักการอย่างเคร่งครัดเกินความจำเป็น สำหรับลอจิกทางธุรกิจ, การคำนวณทางการเงิน และการโต้ตอบ API, SOLID เป็นสิ่งจำเป็น

ความเชื่อมโยงระหว่าง SOLID และ Clean Architecture

Clean Architecture (Robert C. Martin, 2012) — การนำ SOLID ไปใช้โดยตรงในระดับชั้นแอปพลิเคชัน SRP กำหนดขอบเขตของ use case (แต่ละ use case — หนึ่งคลาส) OCP ถูกนำไปใช้ผ่านอินเทอร์เฟสของคลังข้อมูล (Data Layer สามารถเปลี่ยนแปลงได้โดยไม่ต้องแก้ Domain) ISP ให้การแยก Use Case เป็นขอบเขต input/output DIP — ทิศทางของการพึ่งพาเข้าไปในชั้น Domain LSP รับประกันว่าการนำไปใช้คลังข้อมูลใด ๆ สามารถแทนที่ได้โดยไม่ทำให้ use case เสียหาย

คำถามที่พบบ่อย

SOLID คืออะไรในคำง่าย ๆ?

SOLID — ห้ากฎสำหรับเขียนโค้ดที่ง่ายต่อการเปลี่ยนแปลง, ทดสอบ และเข้าใจ แต่ละตัวอักษรคือหนึ่งหลักการ: อย่าเขียนคลาสใหญ่ (SRP), อย่าเปลี่ยนโค้ดที่มีอยู่ — เพิ่มอันใหม่ (OCP), อย่าทำลายพฤติกรรมของคลาสสืบทอด (LSP) และอื่น ๆ

หลักการ SOLID ใดสำคัญที่สุด?

SRP (Single Responsibility) ถือว่าสำคัญที่สุดเพราะการละเมิดนำไปสู่ God คลาส — คลาสขนาดใหญ่ที่ทดสอบและเปลี่ยนแปลงได้ยาก อย่างไรก็ตาม หากไม่มี DIP (Dependency Inversion) โค้ดยังคงเชื่อมโยงอย่างแน่นหนา ซึ่งก็สำคัญเช่นกัน

SOLID จำเป็นสำหรับการพัฒนามือถือหรือไม่?

ไม่จำเป็น แต่แนะนำอย่างยิ่งสำหรับโครงการเชิงพาณิชย์ที่มีวงจรชีวิตยาว สำหรับแอปธรรมดา (หนึ่งหน้าจอ, ไม่มีลอจิกทางธุรกิจ) SOLID อาจเกินความจำเป็น สำหรับโครงการที่มี 50+ หน้าจอและ 3+ นักพัฒนา SOLID คือขั้นต่ำที่จำเป็น

จะเกิดอะไรขึ้นถ้าไม่ปฏิบัติตาม SOLID?

ผลลัพธ์: คลาสกลายเป็น «ใหญ่» (1000+ บรรทัด), การเปลี่ยนแปลงในที่หนึ่งทำลายอีกสามแห่ง, การเขียนทดสอบหน่วยเป็นไปไม่ได้, การเพิ่มฟีเจอร์ใหม่ใช้เวลาหลายสัปดาห์แทนที่จะเป็นวัน เมื่อเวลาผ่านไป โค้ดกลายเป็น «Big Ball of Mud» — พันกันและเปราะบาง

จะตรวจสอบได้อย่างไรว่า SOLID ถูฏปฏิบัติตามในโครงการ?

สัญญาณของการปฏิบัติตาม: แต่ละคลาสน้อยกว่า 200 บรรทัด, การเปลี่ยนฟีเจอร์ไม่กระทบ 5+ ไฟล์, สามารถเขียนทดสอบโดยไม่ต้องจำลอง 10 การพึ่งพา, นักพัฒนาใหม่เข้าใจโครงสร้างในหนึ่งวัน เครื่องมือเช่น SonarQube และ detekt ช่วยระบุการละเมิด SRP และ DIP

สรุป

  • SOLID — ห้าหลักการ OOP (SRP, OCP, LSP, ISP, DIP) เพื่อสร้างโค้ดที่ยืดหยุ่นและบำรุงรักษาง่าย
  • SRP — แต่ละเอนทิตีรับผิดชอบหนึ่งงาน, แก้ปัญหา God คลาส
  • OCP — การขยายผ่านพอลิมอร์ฟิซึม, ไม่ใช่การแก้ไขโค้ดที่มีอยู่
  • LSP — คลาสสืบทอดไม่ควรทำลายพฤติกรรมของคลาสฐาน
  • ISP — อินเทอร์เฟสแคบแทนที่จะเป็น «มีดสวิส» สากล
  • DIP — การพึ่งพาสิ่งที่เป็นนามธรรม, การฉีดผ่าน Hilt/Koin ใน Android
  • SOLID จำเป็นสำหรับ Clean Architecture และโครงการมือถือเชิงพาณิชย์

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม