SOLID หลักการพื้นฐานที่โปรแกรมเมอร์ควรรู้ (ตอนที่ 3 – LSP)

มาถึงหลักการข้อที่ 3 ของ SOLID Principles กันแล้วนะครับ และหลักการนี้มีชื่อว่า Liskov Substitution Principle (LSP) ซึ่งถูกตั้งตามชื่อของผู้ที่คิดหลักการนี้ขึ้นมาเป็นคนแรก Barbara Liskov [1] และในภายหลังก็ถูกนำมารวมอยู่ใน SOLID Principles ด้วย มาดูกันครับว่าหลักการนี้พูดถึงอะไรบ้าง

สารบัญสำหรับตอนอื่นๆ

Liskov Substitution Principle (LSP)

ในบทความของ Barbara Liskov ที่ถูกตีพิมพ์เมื่อปี 1987 หลักการนี้ได้กล่าวไว้ว่า

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

ซึ่งในภายหลัง Robert C. Martin หรือ Uncle Bob ของพวกเราก็ให้คำนิยามที่ฟังดูง่ายขึ้นสำหรับหลักการนี้ใน SOLID Principles ว่า

Subtypes must be substitutable for their base types.

และนั่นก็หมายความว่า

ถ้าเรามีคลาส T1 ซึ่ง extend คลาส B แล้ว ที่ใดก็ตามที่มีคลาส B ถูกใช้งานอยู่จะต้องสามารถสับเปลี่ยนไปใช้คลาส T1 ได้โดยที่ต้องยังคงสามารถใช้งานได้เหมือนเดิม (ไม่มี Error)

มาถึงจุดนี้แล้วเจอกันแต่คำนิยามและทฤษฎีทั้งหลายก็ยังอาจจะงงๆ กันอยู่ เรามาลองดูตัวอย่างกันซะหน่อยดีกว่าครับ

เราเริ่มต้นกันด้วยคลาส OrderProcessor  ซึ่งมีหน้าในที่การจัดการ การสั่งซื้อสินค้าที่เข้ามา และขั้นตอนหนึ่งที่ต้องทำก็คือเก็บข้อมูล รายการการสั่งซื้อสินค้า (Order Log) โดยเราจะสมมุติว่าในช่วงแรกเราจะบันทึกรายการทั้งหมดลงในไฟล์ CSV และใช้ Excel เปิดดูรายการทั้งหมด ก็จะได้คลาสประมาณนี้

เรียกใช้งานโดย

3 เดือนผ่านไปปรากฏว่าธุรกิจเราเจริญเติบโตขึ้น และต้องการที่จะเปลี่ยนการเก็บข้อมูลลง Database แทน ทำให้เราต้องเพิ่ม Class ใหม่ชื่อว่า DbOrderRepository

และเนื่องจากคลาส DbOrderRepository  ต้องใช้ Method ชื่อ connect  ในการสร้างการเชื่อมต่อ (Connection) ทำให้เราต้องแก้คลาส OrderProcessor  เป็น

หลังจากนั้นเมื่อเราสลับ  DbOrderRepository  เข้าไปแทน CsvOrderRepository  ตัว App ก็ยังคงสามารถใช้งานได้ปกติ

แต่เดี๋ยวก่อน…

แล้ว…ถ้าเรามีความจำเป็นต้องเปลี่ยนการบันทึกสินค้ากลับไปใช้ CsvOrderRepository  ล่ะ… นั่นก็หมายความว่าตัวโปรแกรมก็จะเกิด Error ขึ้นทันทีเนื่องจากว่า CsvOrderRepository  นั้นไม่มี Method ที่ชื่อ connect  นั่นเอง

จากสถานการณ์ด้านบนเมื่อเราต้องการสลับ CsvOrderRepository  และ DbOrderRepository  ที่ Implement OrderRepositoryInterface  เหมือนกันกันแต่ไม่สามารถทำได้โดยทันทีหรือต้องแก้โค้ดเพื่อให้โปรแกรมใช้งานได้นั่นก็คือการฝ่าฝืนหลักการ LSP นั่นเอง

เวอร์ชันหลัง Refactor

จากหลักการ LSP ที่เราต้องสามารถแทนที่หรือสลับ Class ที่เป็น Type เดียวกันซึ่งกันและกันได้ จะเห็นว่าปัญหาของเราก็จะอยู่ที่ Method connect และแน่นอนเราควรจะหาทางกำจัดการสร้าง Connection ของ Database ออกไปจาก OrderProcessor  นั่นก็คือการย้ายการสร้าง Connection ออกไปไว้ในตัว DbOrderRepository  นั่นเอง ซึ่งก็จะได้เป็น

// DbOrderRepository (v.1)

จากโค้ดด้านบนจะทำให้เราสามารถลบ

ออกไปได้

และก็นั่นจะทำให้ OrderProcessor  นั้นสามารถทำงานได้ไม่ว่าเราจะส่ง  CsvOrderRepository  หรือ  DbOrderRepository เข้ามา ซึ่งนั่นก็จะเป็นไปตามหลัก LSP

เพิ่มเติม

อย่างไรก็แล้วแต่…จากโค้ดด้านบนยังคงมีบางส่วนที่ฝ่าฝืนหลัก SRP ซึ่งบอกว่าคลาสนั้นควรจะมีหน้าที่เดียวเท่านั้น แต่เราจะเห็นได้ว่าถึงจุดนี้ DbOrderRepository  ของเราไม่ได้มีหน้าที่เพียงแค่การรับและเขียนข้อมูลกับฐานข้อมูลเท่านั้น แต่ยังมีหน้าที่ในการสร้างเชื่อมต่อกับฐานข้อมูลอีกด้วย ดังนั้นเราควรที่จะแยกหน้าที่ในการสร้างการเชื่อมต่อกับฐานข้อมูลไปไว้ที่ DatabaseConnector  ได้ดังนี้

// DbOrderRepository (v.2)

บทสรุป

ในความเห็นส่วนตัวของผมหลักการข้อนี้จริงๆ แล้วเหมือนจะเป็นข้อควรระวังและเตือนใจซะมากกว่า ว่าการ Extend หรือ Implement คลาสใดๆ นั้นเราต้องคิดเสมอว่าถ้าหากเรา Override method ใดๆ แล้วการทำงานของ Method นั้นก็ยังต้องเป็นไปในทิศทางเดิม และเมื่อเราสลับ Class ใหม่ไปใช้แทนที่ๆ Class ที่มีอยู่เดิม การทำงานของโปรแกรมยังคงต้องเป็นเหมือนเดิมโดยที่ไม่ต้องเพิ่มโค้ดหรือ Logic ใดๆ เข้าไป

อ้างอิง

  1. https://en.wikipedia.org/wiki/Liskov_substitution_principle
  2. https://leanpub.com/laravel
prapat