เรื่องของตัว L (Liskov Substitution) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.09.41 AM.png

มาถึงหลักการข้อ 3 ของ SOLID กันแล้วนะครับ สำหรับตัว L ซึ่งย่อมากจาก Liskov Substitution Principle (LSP)  ซึ่งกล่าวด้วยความ Abstact อีกแล้วว่า

Function ที่ใช้ reference หรือ Object ของ คลาสแม่ (Base Class) นั้นควรจะต้องสามารถใช้ Object ของคลาสลูกมันได้โดยไม่ต้องรู้รายละเอียดหรือข้อจำกัดของมัน

มาถึงตรงนี้ก็งงอีกเช่นเคยใช่มั้ยครับ เนื่องจากหลักการนี้เท่าที่ผมลองศึกษาดูก็มีการตีความหมายไปได้หลายทาง แต่ผมขออธิบายในสิ่งที่ผมเข้าใจละกันครับ ถ้าใครอยากเข้าใจมากขึ้นแนะนำให้ลองค้นคว้ามเพิ่มเติมดีกว่าครับ

…เอาล่ะ เราเป็น Developer เราก็มาลองดู Code กันดีกว่าครับ

*เช่นเดิม Code เขียนด้วย TypeScript

ก่อนอื่นเลยผมมี Class Phone ซึ่งมี  2 Methods คือ call() กับ installApp()

    class Phone {
        public call(){
            console.log('Make call...');
        }
        public installApp(){}
    }

ผมก็ได้ทำการเพิ่ม Class มาอีก 2 Classes คือ SmartPhone กับ FeaturePhone โดยการสืบทอดมาจาก Phone

แต่บังเอิญว่า FeaturePhone ดัน install App ไม่ได้เลยต้องทำการ Throw Exception ออกไป

    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        public installApp(){
            throw new Error('Feature Phone does not support install app...');
        }
    }

ถ้าผมที่ Code ฝั่งที่เรียกใช้ Phone จะเป็นแบบนี้ครับ

let phone = new Phone();
phone.installApp(); // ก็  Work ดี

let smPhone = new SmartPhone();
smPhone.installApp(); // ก็ Work ดี

let ftPhone = new FeaturePhone();
ftPhone.installApp(); // พัง เกิด Exception

ซึ่งจะเห็นได้ว่า ผมต้องมาแก้เป็นแบบนี้ เป็นกรณีพิเศษ เช่นต้องใช้ try/catch

try {
    ftPhone.installApp();
}

และนี่แหละคือการผิดหลักการออกแบบที่ดีของหลักการ LSP เพราะเราไม่สามารถเรียกใช้ Class ลูก (FeaturePhone) ได้แบบที่เหมือนเรียก Class แม่ (Phone) ซึ่งการที่เราจะรู้ได้ว่ามัน Throw Exception นั้นก็ต้องไปดู  Code ที่ถูก Implement ใน Class นั้น ซึ่งถ้าเป็น  Code ที่อยู่ใน Libary หรือพัฒนาโดยคนอื่นล่ะ มันก็คงจะดูไม่ได้ง่ายๆใช่มั้ยครับ

การจะแก้ไขปัญหานี้ที่ถูกต้องตามหลัก OO คือต้อง Refactor โดยใช้เทคนิคชื่อ Push Down Method ครับ (ก็คือย้าย Method ที่ไม่จำเป็นใน Class แม่ลงไปที่ Class ลูก)
ฉะนั้นผมจะได้ Class ใหม่แบบนี้แทน

    class Phone {
        public call(){
            console.log('Phone install the app...');
        }
    }
    
    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        // additional FeaturePhone feature e.g. multi sim cards
    }

สิ่งที่เปลี่ยนไปคือ

  1. Method installApp() ถูกย้ายลงมาที่ SmartPhone
  2. FeaturePhone จะไม่มี Method installApp() ให้เรียกแล้ว
  3. ทำให้แก้ปัญหาที่ Code ที่ใช้เรียก ftPhone.installApp() แล้วต้องใช้ try/catch ไปได้! 🙂

SOLID The Series:

 

2 thoughts on “เรื่องของตัว L (Liskov Substitution) ในหลักการ SOLID สำหรับคนเขียน OOP”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s