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

Screen Shot 2560-08-27 at 7.02.07 PM

มาถึงตอนสุดท้ายของ SOLID กันแล้วนะครับ ตัวสุดท้ายตัว  D ซึ่งมาจาก Dependency Inversion Principle (DIP) โดยที่ส่วนตัวผมเองนั้นคิดว่าหลักการข้อนี้ค่อนข้างมีประโยชน์และน่าจะเป็นที่รู้จักกันเยอะระดับนึงอยู่แล้วในโลกของการพัฒนา Software ช่วงนี้

DIP นั้นกล่าวว่า

Class ที่มีการเรียกใช้  Class อื่นๆนั้นไม่ควรที่จะมีการอ้างอิงถึงตัว Object ของ Class ที่ถูกเรียกตรงๆ แต่ควรที่จะเรียกผ่าน Interface หรือ Pointer ของ Base Class

เป้าหมายที่สำคัญของหลักการนี้เพื่อทำให้ Code นั้นมีการผูกติดกันให้น้อยที่สุดกับสิ่งที่เป็น Dependency ของ Class เรา

คำว่า Dependency ในกรณีนี้ก็คือ เช่น เรามี Class SmartPhone แล้วมี Data Member เป็น Object ที่สร้างจาก Class BluetoothDevice เช่น  Code ตัวอย่างนี้

class BluetoothDevice {
 public connect(){ /* do low level network tasks */}
 public scan(){}
} 

class SmartPhone {
 private bt : BluetoothDevice; // มีการถือ object BluetoothDevice ใน Class
 public init(){
   this.bt = new BluetoothDevice(); // มีการสร้าง Object ใน Class
 }
 public connectBluetooth(){
   this.bt.connect();
 }
}

จะเห็นว่า Code ข้างต้นนี้ออกแบบได้ถูกหลัก Single Responsibility แล้วเพราะเราทำการแยก Class ของ SmartPhone และ BluetoothDevice ออกจากกันตามหน้าที่ 🙂 ตามรูปด้านล่าง

Screen Shot 2560-08-28 at 11.10.51 PM

Refactor

แต่ถึงอย่างนั้นด้วย DIP เราจะทำให้ Class นี้มี Design ที่ดีขึ้นไปอีกโดยที่เราจะใช้ Interface เป็นตัวกลางในการ Reference เช่นรูปด้านล่างนี้

Screen Shot 2560-08-28 at 11.12.12 PM

ดังนั้น Code ที่ทำการ Refactor โดยการ Inversion แล้วก็จะมีหน้าตาแบบนี้แทนครับ

 interface IBluetooth {
   connect();
   scan();
 }
 class BluetoothDevice implements IBluetooth { // class นี้ทำการ implements interface
   public connect(){ 
     /* do low level network tasks */
     console.log('bluetooth connect');
   }
   public scan(){}
 } 
 class SmartPhone {
   private bt : IBluetooth; // อ้างถึง Bluetooth ผ่าน Interface ไม่ใช่ Object
   public init(bt : IBluetooth){ // มีการรับ Object ที่มี Interface IBluetooth เป็น Parameter
     this.bt = bt;
   }
   public connectBluetooth(){
     this.bt.connect();
   }
 }

จาก Code ด้านบนนี้จะเห็นว่า Class SmartPhone นั้นไม่ได้มีการอ้างอิงไปถึง Object ของ Class BluetoothDevice ตรงๆแล้วแต่จะใช้ Interface แทนครับ ทำให้การที่จะเรียกใช้ Method ที่ถูกต้องของ BluetoothDevice นั้นจะต้องมีการสร้าง Instance ของมันให้ถูก

.

.

Code ของฝั่งที่จะสร้าง Object หรือ Instance จาก Class SmartPhone จึงต้องเปลี่ยนไปเป็นเช่นนี้

let sp = new SmartPhone();
sp.init(new BluetoothDevice()); // ต้องสร้าง Object ของ BluetoothDevice ส่งไปให้
sp.connectBluetooth();

ซึ่งการทำการส่ง Object ที่เป็น Dependency ของอีก Class นึงเข้าไปแบบนี้คือการทำสิ่งที่เรียกว่า Dependency Injection ( DI ) นั่นเอง

.

Testable Code

ข้อดีที่เราจะได้รับจากการทำ DIP ก็คือการทำให้ Code ของเรานั้น Unit Test ได้ง่ายขึ้น

ลองคิดดูครับว่าถ้า Class BluetoothDevice ตัวจริงนั้นต้องไปต่อ Network จริงๆ แล้วเราจะเขียน Unit Test ของ Class SmartPhone ได้ยังไง

.

การทำ DIP นั้นจะทำให้เราสามารถ Inject Mock Dependency ลงไปได้เช่นดังตัวอย่างในรูปนี้

Screen Shot 2560-08-28 at 11.27.36 PM.png

class MockBluetoothDevice implements IBluetooth {
 public connect(){
   console.log('just mock connect');
 }
 public scan(){
   console.log('just mock scan');
 }
}

Code ตอนเขียน UniTest

let sptest = new SmartPhone();
sptest.init(new MockBluetoothDevice()); // ส่งตัวปลอม(Mock) ของ BluetoothDevice ไป
sptest.connectBluetooth(); // ไม่ต้องไปต่อ Network จริงๆ

ก็จะเห็นว่า DIP นั้นช่วยทำให้ Code เราเขียน Test ได้ง่ายขึ้น แต่ทั้งนี้ทั้งนั้นทุกวันนี้เราจะเห็นว่ามี Framework ที่ช่วยทำ Dependency Injection มากมายเลยนะครับ แต่ผมอยากให้ทุกคนทราบกันว่านี่คือเบื้องหลังของมันรวมถึงที่มาที่ไปด้วย

เอาล่ะ มาถึงตรงนี้เป็นอันว่าจบหลักสูตร SOLID กันแล้วนะครับ สำหรับคนที่คิดว่ามันเยอะไปสำหรับ 5 หลักนี้ ผมก็คงแนะนำให้ทบทวน 2 ตัว คือ S กับ D ละกันครับ 🙂 และหวังว่าซีรี่ย์นี่จะเป็นประโยชน์กับหลายๆคนนะครับ

SOLID The Series:

 

 

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