iT邦幫忙

2022 iThome 鐵人賽

DAY 9
1

我們在昨天有留下一個問題,假設今天變成是 AVG的外接口想要去轉成 HDMI,那是不是就要再多一個 AvgToHdmiAdapter 的類別出來並且一樣去繼承 HDMI呢?今天就要利用『Strategy』模式來解決!

Strategy - 定義

定義一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換。Strategy 模式使演算法可獨立於使用它的客戶而變化。

https://ithelp.ithome.com.tw/upload/images/20220919/20136443KqUMDg4OfF.png

(圖片來源:https://www.dofactory.com/img/diagrams/net/strategy.png)

整合 Adapter

我們先回顧一下昨天利用 Output 類別整合所有接口的 UML

https://ithelp.ithome.com.tw/upload/images/20220919/20136443NA5BJTBt9V.png

這邊我想要做以下幾點的改變

  • 讓 Screen 使用到的是 ScreenService 的服務,其中會去判斷如果螢幕的接口規格是電腦沒有的,那就呼叫 Adapter 進行轉換。
  • Adapter 獨立出來並定義為介面,所有轉接類別繼承自 Adapter。這裡會將 Adapter 定為介面的原因主要是我希望轉接頭類別是能夠去自己改寫 Switch() 這個行為,而不是都繼承自一個抽象類別,假設今天有個新的轉接方式可能是透過藍芽或其他非接口對接的方式,這樣在起初的定義上就不會過於侷限在一定要是接口,而是在「轉換」這個行為上。
  • 增加 ScreenSwitchDic 去存取當外部規格對應到電腦的接口規格時,是使用哪個轉接頭做轉接。

新的UML圖

https://ithelp.ithome.com.tw/upload/images/20220919/20136443C2OShYWeZs.png

不囉嗦上Code!

using System;
using System.Collections.Generic;

namespace DAY09_Strategy
{
    class Program
    {
        static void Main(string[] args)
        {
            // 給定螢幕的接口規格
            string screenSpec = "AVG";
            ScreenService screenService = new ScreenService();

            // 判斷是否需要做轉換
            if (screenService.CanConnect(screenSpec))
            {
                Console.WriteLine("不用轉換,直接連線!");
            }
            else
            {
                // 取得對應的電腦接口規格
                var name = ScreenSwitchDic.GetSpec(screenSpec);

                // 使用反射,將對應到的規格字串轉成實體類別
                Type type = Type.GetType($"DAY09_Strategy.{name}");
                var adapter = Activator.CreateInstance(type);

                //使用轉接器,並將所需的轉接類別帶入
                screenService.UseAdapter(screenSpec, (Adapter)adapter);
            }
        }
    }

    public static class ScreenSwitchDic
    {
        // 先定義外部規格對應到電腦的接口規格時,是使用哪個轉接頭做轉接
        public static Dictionary<string, string> switchDic = new Dictionary<string, string>()
        {
            { "AVG", "HDMIAdapter"},
            { "VGA", "USBAdapter"},
            { "DVI", "DisplayPortAdapter"},
        };
        public static string GetSpec(string screenSpec)
        {
            if (switchDic.ContainsKey(screenSpec))
            {
                return switchDic[screenSpec];
            }
            else
            {
                return "找不到對應的規格!";
            }
        }
    }

    public class ScreenService
    {
        // 電腦有的外接規格
        private readonly List<string> computerSpec = new List<string>() { "DisplayPort", "USB", "HDMI" };
        public bool CanConnect(string screenSpec)
        {
            return computerSpec.Exists(x => x == screenSpec);
        }

        // 透過聚合取代繼承的方式,將所需要的轉接規格類別當作參數帶入
        public void UseAdapter(string screenSpec, Adapter adapter)
        {
            adapter.Switch(screenSpec);
        }
    }

    public interface Adapter
    {
        public void Switch(string screenSpec);
    }

    public class DisplayPortAdapter : Adapter
    {
        public void Switch(string screenSpec)
        {
            Console.WriteLine($"已將{screenSpec}轉換成DisplayPort!");
        }
    }

    public class USBAdapter : Adapter
    {
        public void Switch(string screenSpec)
        {
            Console.WriteLine($"已將{screenSpec}轉換成USB!");
        }
    }

    public class HDMIAdapter : Adapter
    {
        public void Switch(string screenSpec)
        {
            Console.WriteLine($"已將{screenSpec}轉換成HDMI!");
        }
    }
}

-結果

https://ithelp.ithome.com.tw/upload/images/20220919/20136443Hv5WEFO5SH.png

模式三大要點

  • 針對介面來設計程式,而不要針對實作來設計程式。
  • 優先使用物件聚合,而不是類別繼承。
  • 考慮設計中什麼應該是可變的。這種方法與關注引起重新設計的原因剛好相反。它不是考慮什麼會迫使設計發生改變,而是要考慮什麼能夠在不引起重新設計的前提下改變。這時主要關注的 就是變化的概念進行封裝,這是許多設計模式的主題。

簡單的小結

透過 Strategy 模式,我們成功解決了昨天提到的問題,現在不用再為每一種不同的轉換規格做特殊化的 Adapter,只需要將我所擁有的接口規格繼承自抽象類別,接著在我們要做轉換時,將需要的接口規格類別帶入,就可以很簡單的進行抽換,並且能夠減少過度繼承的事情發生。

在上述提到的模式三大要點中,最讓我一開始一頭霧水是第二點。不對吧!物件導向中引以為傲的一點就是「繼承」阿,怎麼現在叫我少點使用。但看完 Strategy 模式後,發現原來不是說不要使用繼承,而是要考量到什麼才是我們真正需要關注的。


上一篇
【DAY8】用『新』看物件導向的世界
下一篇
【DAY10】Bridge模式 - 矛盾的解釋
系列文
勇闖秘境!探索物件導向背後的設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言