S.O.L.I.D — 5 หลักการของ Object-Oriented Design
S.O.L.I.D เป็นตัวย่อของหลักการหลักๆ ของการออกแบบโปรแกรมเชิงวัตถุ หรือ Object-Oriented Design ของ Robert C. Martin หรือเรียกกันติดปากว่า Uncle Bob
เมื่อรวมหลักการ S.O.L.I.D รวมเข้าด้วยกันแล้วจะทำให้ Developer พัฒนาซอฟต์แวร์ที่ง่ายต่อการบำรุงรักษา (Maintain) และสามารถพัฒนาเพิ่มเติม (Extend) ได้ในอนาคต ซึ่งทำให้ Developer หลีกเลี่ยง Code Smell, ง่ายต่อการ Refactor code และเป็นส่วนหนึ่งของการพัฒนาซอฟต์แวร์ในแบบ Agile หรือ Adaptive
S.O.L.I.D ย่อมาจากอะไร?
ถ้าเราไม่ได้มองเป็นตัวย่อ เราอาจจะเข้าใจความหมายผิดไป (SOLID แปลว่าของแข็ง ตาม Google translated 555) แต่ที่จริงมันย่อจากหลักการต่างๆ ดังนี้
S — Single-Responsibility principle
O — Open/Closed principle
L — Liskov Substitution principle
I — Interface Segregation principle
D — Dependency Inversion principle
ต่อไปก็จะขยายความของแต่ล่ะหลักการไปทีล่ะหลักการ
S — Single-Responsibility (S.R.P)
เป็นหลักการแรกของ S.O.L.I.D ที่แนะนำ developer ว่าเขียน Class ให้มีเหตุผลเดียวที่จะเปลี่ยนแปลงแก้ไข โดยลุงกล่าวไว้ว่า:
A class should have one, and only one, reason to change.
ถ้าหนึ่ง class มีมากกว่าหนึ่งเหตุผลที่จะเปลี่ยนแปลงแก้ไขมัน นั้นก็หมายความว่ามันมีมากกว่า หนึ่ง ความรับผิดชอบ (Responsibility) คือว่า class นั้นทำหลายๆ อย่างไม่เพียงแต่ทำงานอย่างเดียว เมือเราเจอ class ที่มีลักษณะแบบนี้แล้ว เราควรจะแตก class นั้นออกเป็น class ย่อยๆ ตามความรับผิดชอบของงานนั้นเพียงงานเดียว
ตัวอย่างเช่น class AreaCalculator จะรับค่าเป็น class Circle และมี 2 method ที่ทำหน้าที่ความรับผิดชอบ (sum รับผิดชอบคำนวนผลรวม และ output รับผิดชอบแสดงผลรวมบนหน้าจอ) ดังนั้นทำให้มี 2 เหตุผลในการเปลี่ยนแปลง คือการคำนวนและการแสดงผล ทำให้ไม่ตรงตามหลักการนี้
ดังนั้นเราควรจะแตก class ออกตามความรับผิดชอบ ซึ่งจะเหลือเหตุผลเดียวของแต่ล่ะ class ในการเปลี่ยนแปลงแก้ไข class นั้นๆ
O — Open/Close (OCP)
เป็นหลักการที่สองที่แนะนำ developer ว่าควรเขียนหน่วยของซอฟต์แวร์(Classes, Modules, Functions เป็นต้น) ทั้งหลาย ต้องเปิดต่อการขยาย แต่ปิดต่อการแก้ไข โดย Mayer ได้นิยามไว้เข้าใจง่ายว่า:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
นั้นก็หมายความว่า class ควรจะจะง่ายต่อการขยายความสามารถโดยจะต้องไม่แก้ไข class นั้น ซึ่งจะต้องไม่แก้ไข source code หรือ binary ของ class เดิม
จากตัวอย่างด้านบน ถ้าเราต้องการจะขยายความสามารถของ AreaCalculator ให้สามารถคำนวนรูปแบบอื่นๆ ด้วย เช่นตอนนี้คำนวน Circle ได้ แต่ต้องการขยายให้สามารถคำนวนพื้นที่ของ Rectangle ด้วย เราจะต้องสร้าง method เพิ่มเพื่อให้รองรับความต้องการใหม่นี้ และเพิ่ม method เรื่อยๆ เพื่อรองรับรูปแบบที่เพิ่มขึ้น ทำให้ไม่ตรงกับหลักการนี้ ที่เราต้องมาแก้ source ของ AreaCalculator เสมอ
ดังนั้นเราจะดึง method ที่คำนวนพื้นที่ออกมาสร้างเป็น interface Shape เพื่อให้ class “Circle” และ “Rectangle” สืบทอดมาและใช้เรียกใน class “AreaCalculator”
L — Liskov Substitution (LSP)
เป็นหลักการที่นำแนวคิดของ Liskov ที่เสนอในปี 1987 โดยเป็นหลักการแทนทีของ type ในซอฟต์แวร์ นั้นก็คือคลาสย่อยจะต้องสามารถแทนที่สำหรับคลาสหลักของตัวเอง
Derived classes must be substitutable for their base classes.
Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
Barbara Liskov
ตัวอย่างเช่น สร้างคลาสย่อย VolumnCalculator สืบทอดมาจาก AreaCalculator แต่ทว่า return type เป็น long ซึ่งไม่ตรงหลักการนี้ ที่จะให้คลาสย่อยต้องสามารถแทนที่ด้วยคลาสหลัก
ดังนั้นเราก็เพียงเปลี่ยน return type ของ method “calc” ให้เหมือนกับคลาสหลัก
I — Interface Segregation (ISP)
เป็นหลักการที่ว่า client ควรจะไม่โดนบังคับให้ implement interface โดยที่ไม่ได้ใช้ implement นั้น
Clients should not be forced to implement interfaces they do not use.
นั้นคือ class ที่ implement interface จะต้องไม่โดนบังคับให้ implement method ที่ class นั้นไม่ได้ใช้งาน ดังนั้นจะต้องแบ่ง interface ออกเป็น interface ย่อยตามลักษณะการใช้งาน
ตัวอย่างเช่น เราเพิ่ม method “volume” ของ interface “Shape” ทำให้มี 2 method และทุก class ที่สือบทอด interface “Shape” จะต้อง implement ทั้ง 2 method ซึ่งบาง class อย่างเช่น Square ไม่มีปริมาณดังนั้นไม่จำเป็นต้อง implement method “volume” ดังนั้น ทำให้ไม่ตรงกับหลักการนี้
ดังนั้นเราจะต้องแยก interface ออกเพื่อที่จะให้ class implement เฉพาะสิ่งที่ต้องการเท่านั้น โดยที่ Square จะ implement interface “Shape” เท่านั้น
D — Dependency Inversion (DIP)
เป็นหลักการสุดท้ายของหลักการ S.O.L.I.D กล่าวว่า หน่วยของซอฟต์แวร์จะต้องขึ้นอยู่กับ Abstraction ไม่ใช่ขึ้นอยู่กับ Concretion โดยระบุว่าโมดูลระดับสูงจะไม่ขึ้นกับโมดูลระดับล่างแต่ควรจะขึ้นอยู่กับ Abstraction
Depend on abstractions, not on concretions.
นั้นคือควรจะทำให้หน่วยของซอฟต์แวร์ขึ้นอยู่กับ abstract class หรือ interface แทน ที่จะขึ้นอยู่กับ concrete class
เราก็ใช้ตัวอย่างเดิม AreaCalculator ถ้า method “calc” รับค่าเป็น concrete class “Circle” ทำให้ไม่ตรงกับหลักการนี้
ดังนั้นเราก็สร้าง abstract class “Shape” ขึ้นมาเพื่อให้ method “calc” ไม่ขึ้นอยู่กับ concrete class “Circle” อย่างเดียว ในอนาคตถ้ามีการเพิ่มเติมรูปแบบก็จะไม่ต้องแก้ไข method “calc” อีก
อ้างอิง