Go SOLID - Interface Segregation Principle
Natcha Luangaroonchai
บล็อกนี้เล่าถึงหลักการ Interface Segreagation Principle (ISP) หนึ่งในหลักการของ SOLID ที่กล่าวไว้โดย Robert C. Martin หรือ Uncle Bob ในบทความที่ตอบจดหมายเอาไว้ดังนี้
Keep interfaces small so that users don’t end up depending on things they don’t need.
หมายความว่าพยายามทำให้อินเตอร์เฟสมีขนาดเล็กท่าที่จำเป็นต่อการใช้งานเพื่อผู้ใช้จะได้ไม่ต้องสนใจสิ่งอื่น ๆ ลองมาดูตัวอย่างในแบบของ Go กัน
สารบัญ
- Go SOLID - Single Responsibility Principle
- Go SOLID - Open-closed Principle
- Go SOLID - Interface Segregation Principle
ยกตัวอย่างระบบหนึ่งต้องมีการเก็บล็อกของการทำรายการลงไฟล์ อาจจะประกาศฟังก์ชัน writeLog
หน้าตาแบบนี้
// writeLog writes a given arguments data into the file.
// An error will be returned when log writing failed.
func writeLog(f *os.File, args ...any) error {
...
}
ฟังก์ชันทำงานได้ดีแต่ปัญหามันอยู่ที่การเขียนยูนิตเทส ซึ่งเป็นไปไม่ได้เลยที่จะไม่ต้องเปิดไฟล์จริง ๆ ขึ้นมาเขียนตอนเทส มาลองแก้ปัญหาด้วยการเปลี่ยน *os.File
เป็นอินเตอร์เฟส io.WriteCloser
แบบนี้
func writeLog(f io.WriteCloser, args ...any) error {
...
}
OK ตอนนี้สามารถเขียนยูนิตเทสกับฟังก์ชัน writeLog
ได้แล้ว.. แต่ถ้าพิจารณาจากหลักการ ISP การใช้อินเตอร์เฟส io.WriteCloser
ดูว่าจะเกินความจำเป็นของฟังก์ชัน writeLog
ไปสักหน่อย
หน้าที่หลักของฟังก์ชัน writeLog
คือทำหน้าที่แค่เขียนล็อกไม่จำเป็นต้องทำหน้าที่ปิดไฟล์ ซึ่งว่ากันตามตรงตัวฟังก์ชันไม่จำเป็นต้องรู้ด้วยซ้ำว่าตัวแปร f
นั้นจริง ๆ แล้วเป็นไฟล์พอยน์เตอร์หรือไม่ ดังนั้นถ้าเปลี่ยนจาก io.WriteCloser
เป็น io.Writer
น่าจะตรงกับสิ่งที่ฟังก์ชัน writeLog
ต้องการมากกว่า
func writeLog(f io.Writer, args ...any) error {
...
}
ถ้าเขียนโค้ดเต็ม ๆ ในส่วนของการเรียกใช้งานฟังก์ชัน writeLog
จะได้ออกมาประมาณนี้
package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
)
func main() {
f, err := os.OpenFile(filepath.Join(os.TempDir(), "application.log"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
err = writeLog(f, "hello, world!")
if err != nil {
log.Fatal(err)
}
}
func writeLog(f io.Writer, args ...any) error {
_, err := fmt.Fprint(f, args...)
return err
}