maintainable software | https://unsplash.com/photos/Jt01DmHeiqM

หลักการของแข็ง? SOLID Principles

Supawit R
odds.team
Published in
3 min readOct 1, 2020

--

SOLID ไม่ได้หมายถึงของแข็งแต่อย่างไร เป็นเพียงคำพ้อง เพื่อให้ง่ายต่อการจำ โดย คำ ๆ นี้ย่อมาจากหลักการ 5 ข้อ

SOLID ย่อมาจาก
> S — Single-responsibility Principle
> O — Open-closed Principle
> L — Liskov Substitution Principle
> I — Interface Segregation Principle
> D — Dependency Inversion Principle

ในภาษาโปรแกรมเชิงวัตถุ (Object-oriented programming) มี SOLID ที่เป็นแนวคิดยอดนิยม 5 ข้อ ที่มีจุดมุ่งหมายเพื่อที่ทำให้การดีไซน์ของซอฟต์แวร์มีความเข้าใจมากขึ้น ยึดหยุ่น และดูแลรักษาได้ โดยผู้ที่ได้นำเรื่องนี้มาเผยแพร่และมีชื่อเสียงคือ Robert C Martin

ก่อนที่จะไปเข้าใจ SOLID Principles มาเข้าใจปัญหาของการเขียนโปรแกรมในปัจจุบันก่อน โดยมีหลัก ๆ อยู่ 2 เรื่อง คือ

1. High Coupling

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

2. Low Cohesion

ลักษณะซอฟต์แวร์ที่การทำงานเรื่องเดียวกัน ถูกกระจายไปทั่ว ทำให้ยากต่อการบำรุงรักษา ทำเรื่องเดียวแต่ต้องไปตามแก้หลาย ๆ module

ตามที่กล่าวไปข้างต้นแสดงให้เห็นถึงลักษณะของซอฟต์แวร์ที่ยากต่อการบำรุงรักษา Low Cohension และ High coupling และในอีกมุมนึงก็แสดงให้เห็นถึงแนวทางการออกแบบซอฟต์แวร์ที่ดี High Cohension และ Low coupling ตามรูปที่แสดงด้านล่าง

ลักษณะของ Cohension และ Coupling ใน 2 มุมมอง | https://slideplayer.com/slide/14764193

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

Single-responsibility Principle (SRP)

คลาสนึงควรมีแค่เหตุผลเดียวที่จะแก้มัน หมายความว่า 1 คลาสควรทำแค่งาน ๆ เดียว อย่าเอาเรื่องอื่นมาปน

ตัวอย่าง สมมติเรามีรูปทรงที่หลากหลาย แล้วต้องการผลรวมพื้นที่ของรูปทรงทั้งหมด

shape creation

อยากแรกคือการสร้าง class สำหรับรูปทรง ที่มีการส่งข้อมูลเฉพาะของรูปทรงนั้น ๆ ผ่าน constructor ถัดไปเราจะไปสร้าง AreaCalculator ที่จะถูกเขียนลอจิกที่เกี่ยวข้องกับการหาผลรวมของพื้นที่

area calculation and return output

การใช้งาน AreaCalculator เราจะสร้าง array ของรูปทรง แล้วให้แสดงผลรวมพื้นที่ผ่านทาง output

ปัญหาคือฟังก์ชัน output ของ AreaCalculator จัดการผลลัพธ์ที่จะออกมา แล้วจะทำยังไงถ้าผู้ใช้อยากได้ผลลัพธ์ออกมาเป็น json หรืออะไรอย่างอื่น?

ดังนั้นลอจิกที่ถูกจัดการด้วย AreaCalculator ขัดกับ SRP สุดท้าย class ควรจะเหลือเพียงการคำนวณผลรวมของรูปทรงก็พอ ไม่จำเป็นต้องสนใจว่าผู้ใช้ต้องการข้อมูลเป็น json หรือ HTML

ดังนั้นเพื่อแก้ปัญหาการจัดการการแสดง เราจะสร้าง class SumCalculatorOutputter เพื่อจัดการเรื่องรูปแบบการแสดงผลนั้นไป

SumCalculatorOutputter จะทำงานลักษณะนี้

ตอนนี้ ลอจิกอะไรก็ตามที่เกี่ยวกับการแสดงผล จะถูกจัดการผ่าน SumCalculatorOutputter

Open-closed Principle

Object ควรจะเปิดให้ขยายความสามารถต่อ ๆ ไปได้ แต่ไม่ควรไปแก้ไขโค้ดเดิม

ลองมาดูตัวอย่างของ AreaCalculator ที่มีฟังก์ชัน sum กัน

ถ้าเราต้องการให้ฟังก์ชัน sum สามารถหาผลรวมพื้นที่ของรูปทรงอื่น ๆ ได้ เราจะต้องมาเพิ่ม if/else หรือเปล่า? ถ้าใช่มันหมายถึงเรากำลังทำสิ่งที่ตรงกันข้ามกับ Open-closed Principle อยู่

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

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

ตอนนี้เราสามารถสร้างอีกรูปทรงแล้วส่งไปให้คำนวณโดยที่ไม่ต้องไปแก้โค้ดเดิมเลย 🙂 ยังไงก็ตามยังมีอีกปัญหาตามมา เราจะรู้ได้ยังไงว่า object ที่ถูกส่งให้ AreaCalculator เป็น shape จริง ๆ หรือ shape ที่มีฟังก์ชัน area?

การเขียน interface มีความจำเป็นมากในการที่เราจะใช้ SOLID มาดูตัวอย่างการทำ interface สำหรับทุก ๆ รูปทรงกัน

ใน AreaCalculator ฟังก์ชัน sum เราสามารถทำให้ class สามารถเช็คได้ว่ารูปทรงจะต้องเป็น ตัวแปร IShape เท่านั้น ด้วยการปรับ parameter ของ AreaCalculator ให้เป็น Ishape[] ก็ทำให้เรามั่นใจว่ารูปทรงที่ส่งเข้ามามีฟังก์ชัน area

Liskov Substitution Principle

คลาสลูก (subclass) ทุกตัว สามารถเป็นตัวแทนให้คลาสแม่ (parent class) ได้ “แบบไม่แหกกฎแม่”

สมมติว่าวันนึงเราอยากได้ตัวคำนวณผลรวมของปริมาตร ซึ่งเราจะสืบทอดมาจาก AreaCalculator

ใน SumCalculatorOutputter

สิ่งที่เกิดขึ้นคือฟังก์ชัน sum ใน VolumeCalculator จะพังอยู่เนื่องจากไป break กฎของคลาสแม่นั่นคือ sum ของแม่ต้อง return กลับไปเป็น number แต่ sum ของ VolumeCalculator กลับเป็น array

แก้ไขด้วยการส่งข้อมูลที่ถูกต้องกลับไป

โดยที่ summedData คือข้อมูลประเภท number

Interface Segregation Principle

คลาสลูกจะต้องไม่ถูกบังคับให้ implement ฟังก์ชันที่ไม่ได้ใช้

แยก interface ออกเป็นหลาย ๆ ตัวเพื่อให้เหมาะสมกับหลาย ๆ งานแทนที่จะมีเพียง interface เดียวที่ครอบคลุม ปัญหาคือตัวคลาสลูกจะต้องมา implement method ที่ไม่จำเป็น ทำให้นักพัฒนาคนอื่นมาดูก็เกิดความไม่เข้าใจว่าทำไม override แล้วไม่เขียนอะไรใน method

ต่อจากตัวอย่างก่อนหน้า เรามีรูปทรง 3 มิติเกิดขึ้นมา ทำให้เราควรแยกการคำนวณปริมาตรมาไว้ที่รูปทรงด้วย เราจึงเพิ่มฟังก์ชัน volume() ไปที่ interface ที่ชื่อว่า IShape

ทุก ๆ รูปทรง จะต้อง implement ฟังก์ชัน volume แต่อย่างที่เรารู้ square เป็นรูปทรง 2 มิติและไม่จำเป็นต้องมี ฟังก์ชัน volume ดังนั้น interface กำลังบังคับให้ class Square ต้อง implement ฟังก์ชันที่ไม่จำเป็นต้องใช้

ISP บอกว่า แทนที่รวม interface เป็นก้อนเดียว ก็แยกมันออกมาเป็น ISolidShape ที่มี volume และรูปทรง 3 มิติ เช่น ลูกบาศก์ (cube) สามารถ implement interface นี้

Dependency Inversion Principle

เราควรขึ้นอยู่กับ abstraction ไม่ใช่ concreation — สิ่งที่เป็น high level ไม่ควรขึ้นอยู่กับสิ่งที่เป็น low level module

หลักการข้อนี้เป้าหมายคือเพื่อลดการผูกมัดกับการทำงานบางอย่าง (decoupling) อ่านแล้วงงละซิ ไปดูตัวอย่างกัน

จากโค้ดตัวอย่างจะเห็นว่า class รับข้อมูลเป็น MySQLConnection ตรงนี้เรามองว่าเป็น low level module และ PasswordReminder เป็น high level module

ลองนึกภาพดูว่าหากต้องเปลี่ยน Database เป็นเจ้าอื่นละ สิ่งที่จะเกิดขึ้นคือต้องมาแก้โค้ด ไม่ก็สร้าง duplicate code ส่งผลให้ break หลักการข้อ Open-closed Princicple ไปด้วย

แนวทางจัดการคือลองนำ interface มาใช้

ที่นี้ลองปรับ class ให้ติดต่อกับ interface ที่เราสร้างขึ้นใหม่แทน

ตอนนี้เราก็จะเห็นแล้วว่าทั้ง high level module (PasswordReminder) และ low level module (MySQLConnection) ขึ้นอยู่กับ abstraction ไปแล้ว (IDBConnection)

ครบหมดแล้วกับ SOLID Principles ทั้ง 5 ตัว ถือว่าเป็นแนวทางที่ควรค่าแก่การไปปรับใช้กับงานที่ทำอยู่ หากผิดพลาดอะไรขออภัยนะตรงนี้ หรือสงสัยอะไรพิมพ์ถามไว้ได้เลย จะตอบเท่าที่ตอบได้ครับ 😆 ขอบคุณครับ

ขอบคุณข้อมูลจาก

https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

Special Thanks

ขอขอบคุณ “สำนักงานส่งเสริมเศรษฐกิจดิจิทัล (depa)” และคณาจารย์ “คณะเทคโนโลยีสารสนเทศ มจธ. (SIT)” ที่ให้การสนับสนุน “ทุนเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล (KMUTT-depa)” ซึ่งเป็นทุนที่มอบความรู้ ทักษะและโอกาสดีๆ กับผมอย่างมาก~

--

--

Supawit R
odds.team

a developer who love to learn, read, and sleep.