HeadFirst設計模式(一)策略者模式

来源:https://www.cnblogs.com/zs10/archive/2019/07/03/11123533.html
-Advertisement-
Play Games

最近在看HeadFirst設計模式一書,作為一個半路出家的程式員,感覺很多東西需要學習,學習的路程中有些東西學了當時覺得理解了,但日常工作中沒有使用到漸漸的自己就忘記了。 上面就是寫者系列的博客的原因,主要是為了鞏固知識,忘記在那個博主那邊看過這麼一句話,知識學了後總結了才變成自己的。 策略者模式 ...


最近在看HeadFirst設計模式一書,作為一個半路出家的程式員,感覺很多東西需要學習,學習的路程中有些東西學了當時覺得理解了,但日常工作中沒有使用到漸漸的自己就忘記了。----------------------上面就是寫者系列的博客的原因,主要是為了鞏固知識,忘記在那個博主那邊看過這麼一句話,知識學了後總結了才變成自己的。

 

策略者模式----定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

當然這隻是理論的東西,說實話我現在都沒理解這個理論是啥,下麵我用代碼和比較直觀的語言給大家介紹一下策略者模式,希望能有所收穫。

模擬情節:

    Joe上班的公司有一套鴨子游戲的系統:SimUDuck,游戲中有各種鴨子,一邊游泳,一邊呱呱叫。此系統使用了標準的OO(面向對象)技術。設計一個鴨子的超類(也就是父類-Duck),並讓各種鴨子繼承此超類。

此時由於需求的改變需要增加飛行的功能,Joe直接在超類中添加fly()方法:

 1   public abstract class Duck
 2     {
 3         public  void quack()
 4         {
 5             Console.WriteLine("我會叫");
 6         }
 7 
 8         public void swim()
 9         {
10             Console.WriteLine("我會游泳");
11         }
12 
13         public void fly()
14         {
15             Console.WriteLine("我會飛");
16         }
17         public abstract void display();
18     }
19 
20     public class MallardDuck : Duck
21     {
22         public override  void  display()
23         {
24             Console.WriteLine("我是一隻綠頭鴨");
25         }
26 
27     }
28     public class ModelDuck : Duck
29     {
30         public override void display()
31         {
32             Console.WriteLine("我是一隻模型鴨子");
33         }
34     }
35     public class RedheadDuck : Duck
36     {
37 
38         public override  void display()
39         {
40             Console.WriteLine("我是一隻紅頭鴨");
41         }
42     }
43 
44     class Program
45     {
46         static void Main(string[] args)
47         {
48             Duck  modelDuck=new ModelDuck();
49             modelDuck.quack();
50             modelDuck.display();
51             modelDuck.fly();
52             Console.ReadLine();
53         }
54     }

此時問題就來了,模型鴨子(ModelDuck)由於也是繼承於超類也具備了飛行的功能,顯然繼承在這裡並不是一個很好的解決方案。這裡在超類中用到了抽象的方法,有個小技巧對於新學者來說經常搞不清抽象方法的語法,記住抽象方法是沒有身體的,也就是說他是沒有方法體的。

這時候就不能用繼承了,這時候有人提出把fly()和quack()方法提到介面IFlyable與IQuackable中,這樣可以飛行的鴨子實現介面,不可以飛行的模型鴨子就不實現介面,錶面上看來是滿足了要求,但這樣的話不僅僅fly()在每個可以飛行的鴨子類中都要實現一邊造成代碼重覆太多,如果要將可以飛行的鴨子的飛行行為稍微改動一下的話,那麼面臨的將是災難,沒給方法你都需要改動一下。

此時,你是否期望一種設計模式能來救你於苦海,這裡我先賣一個關子,接下來我會用老方法找出一個解決之道,“採用良好的OO軟體設計原則”;

通過上面的例子的一步步學習,我想我們應該知道繼承並不能很好的解決問題,因為鴨子的行為在子類中是不斷地變化,所以讓所有的子類都有這些行為是不恰當的。介面IFlyable與IQuackable中一開始看起來還挺不錯,解決了問題但是由於介面不具備實現代碼,所以實現介面無法達到代碼的復用。這意味者你無論何時要修改某個行為,都會造成你需要修改所有實現該介面的類中修改他們,這樣很容易造成錯誤。有個設計原則能很好的解決此問題,

設計原則:找出應用中可能需要變化的部分,把他們獨立出來,不要和那些不需要變化的代碼混合在一起。


這是我們介紹的第一個設計原則,在後面的學習中我還會介紹被的設計原則,我們先對著上面的例子看看那部分是改變的,都的鴨子的行為是不斷的變化的我們需要把行為單獨提出來,我們知道Duck類內的fly()和quack()會隨著鴨子的改變而不斷的改變,為了把這兩個行為從Duck類中分開,我們將把他們從Duck類中取出來,建立一組新類來代表每個行為。

設計鴨子的行為

如何設計那組實現飛行和呱呱叫的行為呢?

首先我們希望一切具有彈性,一開始的鴨子系統正是因為沒有彈性,才讓我們走上這條路,我們還行能夠‘指定’行為到鴨子的實例。我們想要產生一個新的綠鴨子的實例的時候,並指定特定‘類型’的飛行行為給它,乾脆順便讓鴨子的行為可以動態的改變好了。換句話說,我們應該在鴨子類中包含設定行為的方法,這樣就可以在‘運行時’動態地‘改變’綠頭鴨子的行為。

有了這些目標的需求,接著我們來看我們的第二給設計原則

設計原則:針對介面編程,而不是針對實現編程

從現在開始,鴨子的行為將被放在分開的類中,此類專門提供某行為介面的實現。這樣的話和之前的做法有了明顯的不同,之前的做法是:行為來自Duck超類的具體實現,或者繼承某給介面並由子類自行實現行為。這兩種做法都是依賴於‘實現’造成我們的系統不再有彈性,很難更改別的代碼。

在我們新的實現中,鴨子的子類將使用介面(IFlyBehavior與IQuackBehavior)所表示的行為,也就是實現此介面的行為類(FlyNoWay等)具體的行為的實現現在編寫在行為類中。這樣的設計就使得我們的系統具備的彈性。

這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象復用,因為這些行為已經與鴨子類無關了。而我們也可以新增一些行為,不會影響到既有的行為類,也不會影響‘使用’到飛行行為的鴨子類。

整合鴨子的行為

關鍵在於,鴨子現在會將飛行和呱呱叫的動作‘委托’(delegate)別人處理,而不是使用定義在Duck類(或子類)內的呱呱叫和飛行方法。

做法的這樣的:

①首先,在Duck類中‘加入兩個實例變數’,分別為”iFlyBehavior“與”IQuackBehavior“,聲明為介面類型(而不是具體實現介面的類的類型)每個鴨子對象通過動態的實現這些變數以在運行時引用正確的行為類型。

我們用兩個相似的方法performFly和performQuack來取代Duck類中fly()與quack()。稍後你就知道原因了。

②現在,我們來實現

 

performQuack();public class Duck
{ IQuackBehavior iQuackBehavior;//每隻鴨子都會引用實現IQuackbehavior介面的對象。 public void performQuack() { iQuackBehavior.quack();//鴨子對象不親自處理呱呱叫行為,而是委托給iQuackBehavior引用的對象。

   }
}

 

Duck對象只要叫iQuackBehavior對象去呱呱叫就可以了,在這部分的代碼中,我們不在關心iQuackBehavior介面的對象到底是什麼,我們只關心該對象知道如何進行呱呱叫就夠了。

3.現在關心如何實現iQuackBehavior與iFlyBehavior的實例變數,看看具體繼承Duck類的子類是怎麼實現的吧

    public class ModelDuck : Duck
    {
        public ModelDuck()
        {
            iFlyBehaviro = new DuckNoFly();
            iQueackBehaviro = new DuckNoQueck();
        }           
        public override void Display()
        {
            Console.WriteLine("我是一隻木頭鴨子");
        }
    }

 在ModelDuck實例化時,它的構造器會把繼承來的iFlyBehavior和iQuackBehavior實例變數初始化成DuckNoFly與DuckNoQuack類型的新實例。(DuckNoFly與DuckNoQuack是介面的具體實現)

現在只剩下一個在運行時動態該百年鴨子的行為的功能還未實現,我們上面是在具體鴨子類的構造器內進行更改鴨子的行為的,如果換一個地方將更改鴨子的行為提取到Duck超類中,那麼只要繼承自該超類的鴨子派生類不都具備了動態更改行為的能力了嗎?

在Duck類中,加入兩個新方法:

    public void setPerformFly(IFlyBehaviro fb)
        {
            iFlyBehaviro = fb;
        }
        public void setPerformQuack(IQuackBehaviro qb)
        {
            iQueackBehaviro = qb;
        }

  從此以後,我們可以‘隨時’調用者兩個方法改變鴨子的行為。下麵我貼上更改後的全部代碼:

 public abstract class Duck
    {
        public IFlyBehaviro iFlyBehaviro;
        public IQuackBehaviro iQueackBehaviro;
        public abstract void Display();

        public void Swim()
        {

        }
        public void performFly()
        {
            iFlyBehaviro.fly();
        }
        public void performQuack()
        {
            iQueackBehaviro.quack();
        }
        public void setPerformFly(IFlyBehaviro fb)
        {
            iFlyBehaviro = fb;
        }
        public void setPerformQuack(IQuackBehaviro qb)
        {
            iQueackBehaviro = qb;
        }
    }
    public class ModelDuck : Duck
    {
        public ModelDuck()
        {
            iFlyBehaviro = new DuckNoFly();
            iQueackBehaviro = new DuckNoQueck();
        }           
        public override void Display()
        {
            Console.WriteLine("我是一隻木頭鴨子");
        }
    }

   public interface IFlyBehaviro
    {
         void fly();
    }
    public interface IQuackBehaviro
    {
        void quack();
    }
    public class DuckNoFly : IFlyBehaviro
    {
        public void fly()
        {
            Console.WriteLine("我不會飛");
        }
    }
    public class DuckCanFlay:IFlyBehaviro
    {
        public void fly()
        {
            Console.WriteLine("我會飛");
        }
    }
    public class DuckNoQueck : IQuackBehaviro
    {
        public void quack()
        {
            Console.WriteLine("我不會叫");
        }
    }
    public class DuckGuaGuaQueck:IQuackBehaviro
    {
        public void quack()
        {
            Console.WriteLine("呱呱叫");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
           
            Duck modelDuck = new ModelDuck();
            modelDuck.Display();
            modelDuck.performFly();
            modelDuck.performQuack();

            modelDuck.setPerformFly(new DuckCanFlay());
            modelDuck.performFly();
            Console.ReadLine();
        }
    }

  封裝行為的大局觀

我們已經深入研究了鴨子模擬器的設計,該是看看整體的格局了:

這是重新設計後的類結構,你所期望的一切都有:鴨子繼承Duck,飛行行為實現IFlyBehavior介面,呱呱叫行為實現IQuackBehavior介面。請特別註意類之間的”關係“,關係可以是IS-A(是一個)、HAS-A(有一個)或IMPLEMENTS(實現)。

”有一個“可能比”是一個“更好

”有一個“關係相當有趣:每一個鴨子都有一個IFlyBehavior和一個IQuackBehavior,好將飛行和呱呱叫的委托給它們代為處理。

當你將兩個類結合起來使用,如同本例一般,這就是組合。這種做法和”繼承“不同的地方在於,鴨子的行為不是通過繼承來的,而是和適當的行為對象”組合“來的。

這是一個很重要的技巧,其實也是我們介紹的第三給設計原則:

設計原則:多用組合,少用繼承。

如你所見,使用組合建立系統具有很大的彈性,不僅可以將演算法組封裝成類,更可以”在運行時動態的改變行為“,只要組合的行為對象符合正確的介面標準即可。”組合“被廣泛的應用在很多設計模式中,後面你會經常發現它的身影。

至此,這就是我們學習的第一個模式了。

在上面的內容中我們需要到了幾項內容:

OO基礎:抽象,封裝,多態,繼承。

OO原則:封裝變化;多用組合,少用繼承;針對介面編程,不針對實現編程。

OO模式:策略模式----定義演算法族,分別封裝起來,讓他們直接可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。

初衷,只是想把學習的設計模式自己總結成博客記錄下來,但是好像有點偏離初心更像是一個教別人怎麼理解策略者模式了,水平有限,寫的不是很好,對本內容感興趣的讀者,推薦你閱讀《Head First設計模式》一書,該書很生動的講解了設計模式。我上面所寫的內容也是基於此書。最後感謝您的閱讀。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.如果上一頁是靜態頁面,可以用 history.go(-1)方法; go() 方法可載入歷史列表中的某個具體的頁面。 該參數可以是數字,使用的是要訪問的 URL 在 History 的 URL 列表中的相對位置。(-1上一個頁面,1前進一個頁面)。或一個字元串,字元串必須是局部或完整的URL,該函 ...
  • 最近在做項目時,碰到 safari 瀏覽器不支持location跳轉問題,針對此問題,可以通過 js 模擬點擊時間來解決,代碼如下: ...
  • 最近使用layui的框架時,發現table插件不支持鍵盤快捷鍵切換單元格,花了點時間實現此功能。 分享給有需要的朋友們~~~ 效果圖 代碼: 1.支持 enter,上,下,右鍵 切換單元格,支持隱藏列跳過切換。註:單元格必須開啟了 edit:text 模式,才支持鍵盤切換。使用方法:1.在需要啟用此 ...
  • 跟我學SpringCloud | 第四篇:熔斷器Hystrix 1. 熔斷器 服務雪崩 在正常的微服務架構體系下,一個業務很少有隻需要調用一個服務就可以返回數據的情況,這種比較常見的是出現在demo中,一般都是存在調用鏈的,比如A B C D,如果D在某一個瞬間出現問題,比如網路波動,io偏高,導致 ...
  • 本文來講解一下兩個結構比較相似的行為設計模式:策略模式和狀態模式。兩者單獨的理解和學習都是比較直觀簡單的,但是實際使用的時候卻並不好實踐,算是易學難用的設計模式吧。這也是把兩者放在一起介紹的原因,經過對比和實例介紹,相信應該會一些比較深刻的感知。最後在結合個人的體會簡單聊一下對這兩個模式的一些看法。 ...
  • 小孩玩王者榮耀上癮,偷偷充值了6000多元,我讓騰訊游戲根據實名登記的身份證信息禁止玩游戲,這也是國家網路游戲管理辦法中要求的,禁止未成年人接觸不適宜的游戲,騰訊說做不到。這到底是騰訊游戲系統架構問題還是不作為? ...
  • 官網:www.fhadmin.org 特別註意: Springboot 工作流 前後分離 + 跨域 版本 (許可權控制到菜單和按鈕) 後臺框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+介面 前端頁面:html +vue.js 形式 jquery aj ...
  • 具有“魔性”的通用軟體開發框架,僅有5個普通的控制器類,卻響應著任何複雜的業務場景。其超前的思路、原生態的實現方式為有個性化思路的研發者提供了高度靈活的擴展空間。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...