วันพฤหัสบดีที่ 4 มิถุนายน พ.ศ. 2558

Dependency Injection ต่างจาก Dependency Inversion Principle ต่างกันเยี่ยงไร

Dependency  Injection ต่างจาก Dependency  Inversion Principle อย่างไรนะ
บ่อยครั้งที่มักพบว่า คนส่วนใหญ่มักจะสับสนกันระหว่างคำว่า
Dependency  Injection และ Dependency  Inversion
ยิ่งพูดตัวย่อว่า “DI” แล้ว ยิ่งงงอีกว่า I ย่อมาจากอะไรกันแน่
ดังนั้นมาดูกันว่า
•    ทั้งสองคำมันคืออะไร
•    เหมือนหรือต่างกันอย่างไร
เพื่อไม่ให้เกิดความสับสน
ดังนั้นมาทำความเข้าใจโดยสังเขปกันหน่อย
เริ่มต้นด้วยทั้งสองคำนั้นมีเป้าหมายเหมือนกัน คือ
การจัด code ที่ผูกมัดกันแบบแน่นๆ ออกไป
ช่วยลดความซับซ้อนของ code
ลดไม่ให้เกิด code แบบมั่วๆ ดังที่ชอบเรียกว่า Spaghetti code
ลดเวลาในการดูแลรักษา code ลงไป
Dependency  Inversion Principle (DIP) คืออะไร
คือ D ใน SOLID  เป็นแนวปฎิบัติที่ดีสำหรับการออกแบบในโลกของ Object-Oriented
ซึ่งคุณ Robert C. Martin พูดไว้ในเอกสารเรื่อง DIP
กล่าวไว้ว่า
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
แปลแบบงงๆ จะได้ว่า
Module ที่อยู่ในลำดับที่สูงกว่าต้องไม่ขึ้นอยู่กับ Module ที่อยู๋ในระดับต่ำกว่า
แต่ทั้งคู่จะต้องขึ้นอยู่กับส่วนที่เรียกว่า Abstraction
ส่วนของ Abstraction นั้นจะไม่ขึ้นอยู่กับส่วนรายละเอียด
โดยที่รายละเอียดจะต้องขึ้นอยู่กับส่วน Abstraction
หัวใจคือ Abstraction นั่นเอง
แต่อ่านแล้วมันยังงงๆ มากมาย ดังนั้น มาดูตัวอย่างกันดีกว่า
ตัวอย่างการส่งข้อความแจ้งเรื่อง Promotion  ต่างๆ ผ่านทาง Email
แสดงการทำงานด้วย UML ดังรูป




สามารถเขียน code ได้ดังนี้
public class Email {
   
    public void send() {   
        System.out.println("Email Send -->");
    }

}


public class Notification {
   
    Email email;
   
    public Notification() {
        email = new Email();
    }

    public void notifyPromotion() {

        email.send();
       
    }

}
   
คำอธิบาย
จะพบว่า Class Notification นั้นผูกติดกับ Class Email เป็นอย่างมาก นั่นคือ เมื่อทำการสร้าง Object ของ
Class Notification แล้วจะต้องสร้าง Object ของ class Email ด้วยเสมอ เรียกว่าทั้งสอง class มันผูกติดกันแน่นมากๆ
( Tightly coupling )
เป็นการละเมิดกฏ DIP อย่างแรง ส่งผลให้เมื่อทำการแก้ไขที่ Class Email แล้วมันมีโอกาสที่จะทำให้การทำงานใน Class Notification ทำงานผิดพลาดได้ง่าย คิดว่า developer หลายๆ คนน่าจะพอจินตนาการได้นะ และยิ่งระบบต้องการส่งข้อความไปยังช่องทางอื่นๆ ล่ะ เช่น SMS, Line, Facebook message และ Twitter
•    จะทำอย่างไร ?
•    จะต้องทำการแก้ไขที่ Class Notification เยอะเลยไหม ?
•    ยากหรือเปล่า ?
•    มันจะกระทบกับส่วนการทำงานอื่นๆ ที่เคยทำงานได้หรือไม่ ?
เราจะทำอย่างไรดี เพื่อไม่ให้ทั้งสอง Class ผูกติดกัน ?
จากแนวคิดของ DIP ก็คือ ต้องสร้าง Abstraction Layer ขึ้นมาระหว่างสอง Class
ซึ่งผมทำการสร้าง Interface MessageService ขึ้นมา
ทำการออกแบบได้ดังรูป UML

สามารถเขียน code ของ Abstraction Layer ได้ดังนี้
public interface MessageService {
    public void send();
}
ทำการแก้ไข Class Email ดังนี้
public class Email implements MessageService{   
    @Override
    public void send() {   
        System.out.println("Email Send -->");
    }
}

และแก้ไข Class Notification ดังนี้
/**
 * Dependency Inversion Principle
 * @author Mr.BoonOom
 *
 */
public class Notification {
   
    MessageService messageService;
   
    public Notification() {
        messageService = new Email();
    }

    public void notifyPromotion() {
        messageService.send();
       
    }

}
คำอธิบาย
ทำการเพิ่ม Interface ชื่อว่า MessageService ซึ่งเป็น Abstraction layer เพื่อให้ class Notification ทำการส่งข้อความผ่าน method หรือ operation ที่อยู่ใน Abstraction Layer ซึ่งลดการผูกมัดระหว่าง class Notification และ Email ลงไป
เป็นไปตามแนวคิดของ Dependency  Inversion Principle นะ มันก็ดูดีขึ้นนะ !!
แต่ว่ายังมีปัญหาอยู่ใช่ไหม ?
คำถาม
ตรงไหนล่ะ ?
คำตอบ
สังเกตไหมว่า มีการสร้าง Object ของ class Email ใน class Notification ?
ซึ่งนั่นคือ ปัญหา เราต้องย้ายการสร้าง Object ของ class Email ออกมาซะ
เพราะว่า code มันผูกติดเกินไป และ class Notification ก็ไม่ได้มีหน้าที่สร้าง Object ของ class Email ด้วยนะ
คำถาม
แล้วย้ายออกมาอย่างไรล่ะ ?
คำตอบ
ก็ใช้แนวคิด Dependency  Injection (DI) เข้ามาช่วยไงล่ะ
ถ้าถามว่ามีวิธีการอื่นๆ ไหม ตอบได้เลยว่ามี ดังรูป



แล้ว Dependency  Injection หรือ DI มันคืออะไรล่ะ ?
คือวิธีการเตรียมหรือส่ง Object ที่ต้องการใช้งานเข้าไปโดยไม่ต้องทำการสร้าง Object นั้นขึ้นมาใช้เองซึ่งมันช่วยลดการผูกมักภายใน code ของระบบ
จากตัวอย่างใน Class Notification นั้นมีการสร้าง Object ของ Class Email อยู่
ดังนั้น สิ่งที่เราต้องการคือใน Class Notification จะไม่มีการสร้าง Object ของ Class Email
แต่เราจะส่งเข้าไปให้เลย หรือ เรียกแบบทั่วไปว่า การฉีด หรือ inject Object ของ Class Email เข้าไป
โดยวิธีการ Inject Object เข้าไปนั้น มีอยู่ 3 แบบ คือ
1.    Constructor Injection
2.    Property Injection
3.    Method Injection



มาดูตัวอย่างการ Inject ในแต่ละแบบกันดู เพื่อความเข้าใจมากยิ่งขึ้น
แบบที่ 1 Constructor Injection
เป็นรูปแบบที่ง่ายสุดๆ และมักจะถูกใช้งานกันมากโดยจะทำการส่ง Object ที่ต้องการผ่านไปยัง Constructor ดังนั้นใน class Notification สามารถแก้ไขได้ดังนี้
/**
 * Constructor Inversion
 * @author Mr.BoonOom
 *
 */
public class Notification {
   
    private MessageService messageService;
   
    public Notification(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notifyPromotion() {
        messageService.send();
       
    }

}

ข้อดีของวิธีนี้คือ มันง่ายมากลดงานที่ Class Notification ต้องทำลงไป นั่นคือการสร้าง Object ทำให้ Code ของ
Class Notification กับ Email  ไม่ผูกติดกันครับ  ทำให้ดูแลรักษาง่ายขึ้นนะ ว่าไหม ?


แบบที่ 2 Property Injection
มันคือการสร้าง Setter/Getter method มาเพื่อกำหนดค่าของ Object ที่เราต้องการใช้งานใช้วิธีการนี้เมื่อ Dependency  Object เหล่านั้น ไม่ใช่ตัวหลักในการทำงาน ลองคิดดูว่า ถ้ามี Dependency  Object จำนวนมาก
ถ้าจะใส่ใน Constructor ทั้งหมดไม่น่าจะเหมาะสมเพราะว่าจะเกิด Code Smell ขึ้นมา นั่นคือจำนวน parameter ของ method เยอะเกินไป

ดังนั้นใน class Notification สามารถแก้ไขได้ดังนี้
/**
 * Property Injection
 * @author Mr.BoonOom
 *
 */
public class Notification {
   
    private MessageService messageService;
   
    public void setMessageService(MessageService messageService){
        this.messageService = messageService;   
    }

    public void notifyPromotion() {
        if (this.messageService != null) {
            messageService.send();
        }

       
    }

}


แบบที่ 3 Method Injection
ทำการส่ง  Dependency  Object มายัง method ที่ทำงานเลยทำให้แต่ละ method มี parameter ที่แตกต่างกันตามความต้องการไปเลย
นั่นคือส่งเป็น parameter ดังนี้
/**
 * Method Injection
 * @author Mr.BoonOom
 *
 */
public class Notification3 {
   
    public void notifyPromotion(MessageService messageService) {
            messageService.send();       
    }

}

โดยสรุปแล้ว
มาถึงตรงนี้น่าจะพอทำให้เห็นว่า Dependency  Inversion Principle และ Dependency  Injection มันแตกต่างกันตรงไหน
มันส่งเสริมมีเข้ามาช่วยเราแก้ไขปัญหาอะไรโดยที่เป้าหมายของทั้งสอง คือ ต้องการทำให้ code ไม่ผูกมัดกัน
•    ทำให้เราสามารถ reuse ส่วนการทำงานต่างได้ง่าย
•    ทำให้เราสามารถเพิ่มความสามารถต่างๆ เข้าไปได้ง่าย
การใช้ DI นั้นวิธีที่แนะนำ เพราะว่าง่ายที่สุดคือ Constructor Injection แล้วจึงนำอีกสองวิธีหลังมาใช้ เพื่อเสริมการทำงานต่อไป เช่น Object หลักให้ส่งมายัง constructor ส่วน Object เสริมให้ใช้จาก 2 ตัวที่เหลือ เป็นต้น
แต่เชื่อเถอะว่า Code ที่ Developer ส่วนใหญ่เขียนขึ้นมามักจะผูกมัดกันแบบแน่นๆ

ไม่มีความคิดเห็น:

แสดงความคิดเห็น