[DesignPattern]FacadePattern與AdapterPattern

摘要:[DesignPattern]FacadePattern與AdapterPattern

一、前言

之前學習designPattern都是一個一個學,偶然發現其實有些pattern(其實是聽人說的,慚愧),最近一次聽到的比較就是這兩個pattern:Facade跟Adapter
Facade很簡單,而Adapter有兩種實作方式:
(1)Adapter Pattern(Class)
(2)Adapter Pattern(Object)
稍後會比較一下這兩個方式的優缺點,以及跟Facade使用的時機點,跟自已的一些見解與問題

二、Facade Pattern

1.外觀模式(Facade)定義:
為子系統中的一組介面提供一個一致的介面,此模式定義了一個高層介面,這個介面使得這一子系統更加容易使用
2.架構:

3.解說
簡單來說,就是當你遇到一個龐大的系統時,比如有A、B、C的子系統,Client端若有一個功能是會同時用到ABC時,不讓想Client操作這麼複雜,就會再上面再建立一層介面Facade,只要操作Facade,簡化系統操作的複雜度
舉個簡單的例子,像是使用者登入,資料庫會經過幾個步驟,1.帳密是否正確 2.登入後權限 3.該權限能操作的功能,這時候,我們可能就寫一個loginService,以後有其它頁面需要這三個功能時,直接操作loginService即可。這也是經典的三層式架構的基礎概念

4.實例-基金投資
Stock1、2 股票
Reality 房地產
NationDebt1 公債
FundAgent 基金(Facade)
如下圖,透過FundAgent,去操作Stock1、Stock2、Reality、NationDebt1的buy跟sell

5.使用時機
(1)設計初期:將不同的兩個層分離,像是UI、Service、Dao,經典的三層架構
(2)開發階段:所有的子系統會不斷的重構演化越變越複雜,會讓外部(client)在操作上變複雜
(3)維護階段:在維護一個大的系統時,可能這個系統已經很複雜且難懂,在開發新的需求時,又可能會使用到它們,這時候就要建一個Facade,讓新需求與舊功能互動

三、Adapter Pattrn(Class)

1.轉接器模式(Adapter),將一個類別的介面轉換成客戶希望的另一個介面。Adapter將做的原本由於介面不相容而不能一起工作的那些類別,可以一起工作
2.解說:
假設我設計一個影音播放軟體,基本概念是可以播放各種影音格式
先假定只有三種格式mp3、rmvb、wma,一開始只有mp3跟rmvb,後面有新增功能增加wma

.架構圖-class adapter(使用繼承模式)

(1)首先MP3Player跟RMVBPlayer,兩者幾乎一樣,所以只列MP3Player。因為MP3Player跟RMVBPlayer有相同的行為,所以建立一個IMediaPlayer(Interface)來定義
     .介面

    public interface IMediaPlayer
    {
        void Play();
        void Stop();
    }

     .MP3Player

    /// 
    /// 我是舊的系統功能
    /// 
    public class MP3Player : IMediaPlayer
    {
        public void Play()
        {
            Console.WriteLine("play mp3 music");
        }

        public void Stop()
        {
            Console.WriteLine("Stop mp3 music");
        }
        
    }

 

 

(2)舊系統MP3Player跟RMVBPlayer的對外功能為play以及stop,假設在設計WMA格式的時候,因為需求變更,WMA的播放行放改成On跟Off
而新的系統需求V2,都為On跟Off,所以我們也撰寫一個IMediaPlayerV2來定義新系統的行為

    .介面
    

   public interface IMediaPlayerV2
    {
        void On();
        void Off();
    }


    .WMAPlayer
     

    public class WMAPlayer : IMediaPlayerV2
    {
        public void On()
        {
            Console.WriteLine("WMA music is On");
        }

        public void Off()
        {
            Console.WriteLine("WMA music off");
        }
    }
    到目前為止的程式碼,在主程式執行的結果如下圖,可以發現新舊系統的操作行為不相同,這對開發者來說就必須多一個負擔,可能會有人覺得,那把舊的行為改為on跟off就好了,不過根據designPatternd 的原則,程式應該是可擴充而不能修改,而且目前系統只有5支,如果舊的改成on跟off,勢必要改掉IMediaPlay、MP3Player跟RMVBPlayer,以系統比例來說,我就得修改60%的程式了,這應該要避免了(試想如果是1萬支程式?)
    


(3)使用Adapter
   必需建立兩個adapter類別:TranslatorInClassAdapterMP3、TranslatorInClassAdapter。疑!多兩個類別,不能只有一個嘛,太麻煩了吧?的確,這是用繼承方式實現adapter的限制,有人用泛型的方式解決[泛型解決Adapter(class)缺點], 不過這邊就先不聊這塊。
   .TranslatorInClassAdapterMP3

 public class TranslatorInClassAdapterMP3MP3Player,IMediaPlayerV2
    {
        /// <summary>
        /// IMediaPlayerV2
        /// </summary>
        public void On()
        {
            //MP3Player
            base.Play();
        }
 
        /// <summary>
        /// IMediaPlayerV2
        /// </summary>
        public void Off()
        {
            //MP3Player
            base.Stop();
        }
    }

  .TranslatorInClassAdapter
 

   public class TranslatorInClassAdapterRMVB : RMVBPlayer, IMediaPlayerV2
    {
        /// 
        /// IMediaPlayerV2
        /// 
        public void On()
        {
            //MP3Player
            base.Play();
        }

        /// 
        /// IMediaPlayerV2
        /// 
        public void Off()
        {
            //MP3Player
            base.Stop();
        }
    }

   .到這階段,新舊系統在Client端(主程式)開發時,可以發現介面都是一致,不過可能會有個疑惑?只是介面一致而已啊,沒有比較得比較好開發,而且整個系統變也比較複雜,感覺沒有比較優。使用designpatern時,無庸置疑,class數目會變多,系統架構會變比較複雜,但是,優點在於彈性、可擴充,甚至設計的好時,終端開發者只要了解一部分的程式就可以操作,當然也要用對pattern以及對系統了解,下面再加上決策&簡單工廠,讓系統更好開發

  使用adpater前 使用adpater後
client端須了解的class數
IMediaPlayer
RMVBPlayer
MP3Player
IMediaPlayerV2
WMAPlayer
IMediaPlayerV2
TranslatorInClassAdapterRMVB
TranslatorInClassAdapterMP3
WMAPlayer
clientl端撰寫程式行數 9行(扣掉空白行) 9行(扣掉空白行)
 

    

(4)加上決策&簡單工廠模式
    建立一個FactoryPlayer的類別,程式碼【FactoryPlayer】,由於client端要調用的類別有TranslatorInClassAdapterRMVB、TranslatorInClassAdapterMP3、WMAPlayer,而它們共同都實作了IMediaPlayerV2,想當然爾就宣告一個fuction回傳IMediaPlayerV2

            IMediaPlayerV2 player2 = new TranslatorInClassAdapterRMVB();
            IMediaPlayerV2 player3 = new TranslatorInClassAdapterMP3();
            IMediaPlayerV2 player4 = new WMAPlayer();

  .FactoryPlayer

 public class FactoryPlayer
    {


        static public IMediaPlayerV2 GetPlayer(String File)
        {
            IMediaPlayerV2 player = null;
            switch (File)
            {
                case "MP3":
                    player = new TranslatorInClassAdapterMP3();
                    break;
                case "RMVB":
                case "RM":
                    player = new TranslatorInClassAdapterRMVB();
                    break;
                case "WMA":
                    player = new WMAPlayer();
                    break;
                default:

                    break;
            }
            return player;
        }
    }

   最後client在開發的時候,只需要知道IMediaPlayerV2以及FactoryPlayer,至於其它class,如果沒必要就不用去了解,而決策者模式以及簡單工廠模式,得讀者自已去了解,或是以後我再寫一篇介紹

四、Adapter Pattrn(Object)

     在上一大節中,有說如果用class adapter的話,舊系統有10個class,我也得做出相對應的10個Adpter,能不能只用一個就好?可以,使用Object adapter
.架構圖-Object Adapter(使用組合技術)

(1)把原本的ClassAdapter的複製到ObjectAdapter資料夾中,並將namespace改成ObjectAdapter

(2)現在因為有兩個Adapter:TranslatorInClassAdapterMP3跟TranslatorInClassAdapterRMVB,隨意挑一個來改,另外一個就可以刪掉了
我拿Adapter:TranslatorInClassAdapterMP3來修改,程式碼如下
 

 public class TranslatorInClassAdapter : IMediaPlayerV2
    {
        IMediaPlayer _adaptee;

        public TranslatorInClassAdapter(IMediaPlayer adaptee)
        {
            _adaptee = adaptee;
        }
        /// 
        /// IMediaPlayerV2
        /// 
        public void On()
        {
            _adaptee.Play();
        }

        /// 
        /// IMediaPlayerV2
        /// 
        public void Off()
        {
            _adaptee.Stop();
        }
    }

(3)修改FactoryPlayer
  public class FactoryPlayer

    {


        static public IMediaPlayerV2 GetPlayer(String File)
        {
            IMediaPlayerV2 player = null;
            switch (File)
            {
                case "MP3":
                    player = new TranslatorInClassAdapter(new MP3Player());
                    break;
                case "RMVB":
                case "RM":
                    player = new TranslatorInClassAdapter(new RMVBPlayer());
                    break;
                case "WMA":
                    player = new WMAPlayer();
                    break;
                default:

                    break;
            }
            return player;
        }
    }

五、結論