/ OOD

[365 วันแห่งโปรแกรม #day53] Bridge pattern

วันที่ห้าสิบสามของ ‪#‎365วันแห่งโปรแกรม วันนี้เราจะคุยกันเรื่อง Bridge pattern


Bridge pattern

Bridge pattern เป็นแพทเทิร์นแบบ Structural patterns ใช้สำหรับแก้ปัญหาการ couple ระหว่าง abstraction กับ implementation

แต่เดิมนั้น implementation และ abstraction จะผูกกันแน่นเกินไป ทำให้เมื่อเราแก้ abstraction ก็จะกระทบไปยัง implementation ด้วย

คราวนี้เอาใหม่ เขียนทุกอย่างเหมือนเดิมเลย แต่ว่าสร้าง abstraction ขึ้นมาคั่น ระหว่าง target กับ client ตอนนี้ client ก็จะเห็นแค่ abstraction อันใหม่ละ เราจะแก้ abstraction นี้ยังไงก็ได้ โดยการแก้นั้นจะไม่ส่งผลกระทบอะไรกับ target เลย ก็แน่สิ มันเป็นคนละสายเลือดเลย

พอพูดถึงตัวคั่นระหว่าง client กับ target เราก็จะนึกถึง Adapter pattern ที่คุยกันไปครั้งที่แล้ว จริงๆ แล้ว Bridge pattern ส่วนใหญ่ก็ใช้แนวคิดของ Adapter pattern ในการสร้างอีกทีนึง แต่ต่างกันที่จุดประสงค์ตรงที่ Bridge pattern เน้น decouple แต่ Adapter pattern เน้นเชื่อม

พูดไปพูดมาก็งงเหมือนเดิม เดี๋ยวเราไปดูตัวอย่างกันก่อนดีกว่าครับ

ตัวอย่างของการใช้ Bridge pattern

ตัวอย่างของ Bridge pattern ส่วนใหญ่ก็จะยกเรื่องระบบที่ทำงานข้ามแพลตฟอร์มมาให้ดู โดยเฉพาะเรื่อง GUI ก็เพราะว่า Bridgr pattern เน้นเรื่องการเปลี่ยน implementation ตอนรันไทม์ โดยที่ client ยังทำงานได้เหมือนเดิมและก็ไม่ต้องรับรู้อะไรด้วย เช่น เมื่อโปรแกรมของเรารันบนวินโดวส์ก็จะใช้ส่วนติดต่อ GUI ที่เรียก API บนวินโดวส์ ถ้ารันบนแมคก็ใช้ของแมค ตรงนี้ Bridge pattern จะทำให้โปรแกรมของเราสามารถเปลี่ยนไปใช้ implementation ที่ถูกต้องตอนรันไทม์ได้ ผมหารูปของเรื่อง GUI ไม่เจอ ดังนั้นขอใส่ตัวอย่างเป็นเรื่อง Thread Scheduler แทน >< (เอามาจาก SourceMaking)

Thread Scheduler บนแต่ละ platform ก็จะมีการ implement ที่ต่างกันไป และตัว Thread Scheduler ก็ยังอาจจะมีหลายประเภทอีก เมื่อเราเขียนเป็น diagram ก็จะได้ดังนี้

ThreadScheduler

จะเห็นว่าส่วน implementation ล่างสุด เราต้องใส่โค้ดที่เป็น Platform specific ลงไป ยิ่งมีประเภทของ Thred Scheduler หรือ Platform มากเท่าไหร่ เราก็ต้อง implemente เพิ่มเข้าไปเป็นทวีคูณ สมมติว่าเราเพิ่ม Java Platform เข้าไป จะได้ว่า

ThreadScheduler with java

จะเห็นว่าต้องสร้างของ Java ให้ทุก implementation เลยทีเดียว ส่วนนี้แหละครับคือปัญหา abstraction ผูกกับ implementation มากเกินไป ดังนั้นเราจึงต้องนำ Bridge pattern เข้ามาใช้ โดยทำการ refactor ใหม่ ดังนี้

ThreadScheduler Bridge

จากแผนภาพเราแบ่งของเดิมออกเป็นสองฝั่ง ฝั่งซ้ายเรียกว่า Abstraction มีหน้าที่ในการคุยกับ Client ฝั่งขวาเรียกว่า Implementor ก็จะเป็น implementation ของแพลตฟอร์มต่างๆ จะเห็นว่าที่ ThreadScheduler นั้นมีความสัมพันธ์แบบ Has a กับ ThreadScheduler_Implemntation ทางฝั่งขวาด้วย นั่นแปลว่า Abstraction จะเก็บ reference ของ Implementor gอาไว้เพื่อใช้ในการทำงานต่อไป และเราสามารถที่จะเปลี่ยน Implementor ในขณะรันไทม์ได้อีกด้วย

เรามาดู diagram แบบ official กันหน่อย

Bridge pattern

ก็เหมือนแผนภาพที่แล้วเลย แค่เปลี่ยนชื่อให้ตรงกับที่ใช้เรียกเฉยๆ สรุปตอนนี้ใน Bridge pattern จะมีส่วนประกอบ 4 ชนิด คือ

  • Abstraction (abstract class) เป็น interface สำหรับคุยกับ Client ถือ reference ของ Implementor ไว้เพื่อนำไปใช้งาน

  • RefinedAbstraction (normal class) เป็น Implementation ของ Abstraction

  • Implementor (interface) คือ interface ที่เราแยกออกมา

  • ConcreteImplementor (normal class) คือ Implementation ของ Implementor

คราวนี้เวลา Client จะเรียกใช้งาน ก็คุยกับ Abstraction (reference ของ RefinedAbstraction) แล้วก็สั่งงานไป ตัว Abstraction ก็จะทำงานแล้วเรียก ใช้งาน Implementor ตามที่ Client ระบุไว้

ตัวอย่างโค้ด

ตัวอย่างจาก dzone ครับ เป็นเรื่องของรีโมททีวีที่เอาไปใช้ได้กับทีวีหลายยี่ห้อ

ส่วน Implementor และ Concrete Implementor

//Implementor 
public interface TV
{
    public void on();
    public void off(); 
    public void tuneChannel(int channel);
}

//Concrete Implementor 
public class SonyTV implements TV
{
    public void on()
    {
        //Sony specific on
    }

    public void off()
    {
        //Sony specific off
    }

    public void tuneChannel(int channel);
    {
        //Sony specific tuneChannel
    }
}

//Concrete Implementor 
public class PhilipsTV implements TV
{
    public void on()
    {
        //Philips specific on
    }

    public void off()
    {
        //Philips specific off
    }

    public void tuneChannel(int channel);
    {
        //Philips specific tuneChannel
    }
}

ส่วน Abstraction และ Refined Abstraction

//Abstraction
public abstract class RemoteControl
{
    private TV implementor; 

    public void setTV(TV implementor)
    {
        this.implementor = implementor;
    }

    public void on()
    {
        implementor.on();
    }

    public void off()
    {
        implementor.off();
    }

    public void setChannel(int channel)
    {
        implementor.tuneChannel(channel);
    }
}

//Refined abstraction
public class ConcreteRemote extends RemoteControl
{
    private int currentChannel; 

    public void nextChannel()
    {
        currentChannel++;
        setChannel(currentChannel);
    }

    public void prevChannel()
    {
        currentChannel--;
        setChannel(currentChannel);
   }
}

ครบ 2 ส่วนแล้ว ต่อไปก็จะเป็นการนำไปใช้

public stativ void main(String [] args)
{
    RemoteControl remote = new ConcreteRemote();
    remote.setTV(new SonyTV());
    remote.on(); //เปิด TV โซนี่
    remote.setTV(new PhilipsTV());
    remote.on(); //เปิด TV ฟิลิปส์
}

จะเห็นว่าเราสามารถเปลี่ยนไปใช้ implementation อื่นของ TV ได้ โดยยังใช้ remote ตัวเดิม

สรุป

สุดท้ายแล้วก็ยังงงอยู่ดีครับ มันยากเกินไปจริงๆ >< ก็มีคนสรุปเอาไว้ว่าแพทเทิร์นนี้มีไว้สำหรับเปลี่ยน

        A
     /     \
    Aa      Ab
   / \     /  \
 Aa1 Aa2  Ab1 Ab2

เป็น

     A         N
  /     \     / \
Aa(N) Ab(N)  1   2

โอ้ว กระจ่างเลยครับ ภาพหนึ่งภาพนี่มันแทนคำพูดได้เป็นพันคำจริงๆ ด้วย ><

References

Bridge pattern - WikiPedia

Bridge Design Pattern - Sourcemaking

Design Pattern - Bridge Pattern - manit-tree

Bridge Pattern - Bridging the gap between Interface and Implementation - CodeProject

Design Patterns - Bridge Pattern - TutorialsPoint

Bridge Pattern - OODesign

When do you use the Bridge Pattern? - StackOverflow

Bridge - Dofactory

Bridge Pattern - c2

Design Patterns Uncovered: The Bridge Pattern - DZone

Bridge Pattern - AVAJAVA

#‎day53 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...