設計模式之 面向對象的養豬廠的故事,C#演示(二)

来源:http://www.cnblogs.com/hackpig/archive/2016/08/11/5760664.html
-Advertisement-
Play Games

(三) 優先使用聚合,而不是繼承 有一段時間,養豬場的老闆雇用了清潔工人來打掃豬舍。但有一天,老闆忽然對自己說"不對啊,既然我有機器人,為什麼還要雇人來做這件事情?應該讓機器人來打掃宿舍!" 於是,這個需求被提交到了機器人的研發小組。看到這個需求,我們敏感地意識到,這是一個潛藏了更多變化的需求,未來 ...


(三) 優先使用聚合,而不是繼承

有一段時間,養豬場的老闆雇用了清潔工人來打掃豬舍。但有一天,老闆忽然對自己說"不對啊,既然我有機器人,為什麼還要雇人來做這件事情?應該讓機器人來打掃宿舍!"
於是,這個需求被提交到了機器人的研發小組。看到這個需求,我們敏感地意識到,這是一個潛藏了更多變化的需求,未來機器人的功能還可能會不斷增加,於是,我們提取出了一個抽象的機器人介面,並實現了兩個具體的機器人類一-喂豬機器人和清潔機器人。系統的結構如圖V8-1所示。

 

                              圖V8-1

 

這樣一來,老闆希望機器人工作時,可以調用機器人介面的"工作"方法。由於這也是針對介面編程,當老闆需要新的機器人時,只要添加具體的機器人類即可,其他代碼無需變化。這似乎己經解決了我們遇到的問題。
但這裡還存在另外一個問題:圖v8-1 中的繼承結構是在編譯期間就確定了的,在運行期不能發生任何變化。因此,如果養豬場需要一個喂豬機器人和→個清潔機器人,那麼我們必須在養豬場中放進這兩個具體的機器人。依此類推,如果未來養豬場還需要獸醫機器人、屠宰機器人等等,養豬場中不就擠滿了機器人嗎?更為重要的是,每添加一種機器人的類型,主是們就必須改動代碼中的某一個地方,以便把這個機器人放進養豬場中,這就又會違反開閉原則了。在這種情況下,使用聚合的機制能很好地解決問題,因為基於聚合的結構可以在運行期間發生變化。
使用聚合機制的養豬場如圖v10-1 所示。我們把機器人介面改成了功能介面,而清潔功能和喂豬功能實現了這個功能介面。真正的機器人類中聚合了一個功能介面的引用,這樣,我們只需要在養豬場中放進一個機器人,該機器人中聚合了一個喂豬功能,這時它是一個喂豬機器人。當我們需要打掃養豬場時,老闆只需要調用機器人中的"變形"方法,並傳遞一個"清潔功能"對象給機器人,機器人就會像《變形金剛》中的"擎天柱"一樣,大吼一聲"汽車人,變形"就變成了-個清潔機器人了。

                                    圖v10-1

 

面向對象養豬廠V10版本實現代碼如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 namespace PigFactoryV10
 11 {
 12     /*
 13      *  豬悟能的博客
 14      * http://www.cnblogs.com/hackpig/
 15      * 
 16      有一段時間,老闆雇用清潔工人打掃豬廠,但有一天,老闆對自己說“我有了機器人,為什麼還要雇用人打掃豬場?”
 17      於是,軟體團隊必須開發新的清潔機器人品種
 18      這裡,機器人的類型成了變化點
 19      
 20      下麵的代碼使用了聚合方式,把機器人功能變成介面,只要操作員調用“變形”功能,並傳入一個“清潔功能”,機器人就會
 21      由喂豬機器人變身為清潔機器人。
 22      如果未來要增加屠宰機器人,原有代碼也不用修改。
 23      
 24      這裡反映出設計模式的第三個核心設計原則:
 25      
 26      優先使用聚合而不是繼承。
 27      */
 28 
 29     public partial class Form1 : Form
 30     {
 31         public Form1()
 32         {
 33             InitializeComponent();
 34             btn_clean.Click += new EventHandler(btn_clean_Click);
 35             btn_feed.Click += new EventHandler(btn_feed_Click);
 36         }
 37 
 38         void btn_feed_Click(object sender, EventArgs e)
 39         {
 40             adminMan man1 = new adminMan(1, "大潤發養豬場");
 41             this.rtbMsgInfo.Text = man1.Msg;
 42         }
 43 
 44         void btn_clean_Click(object sender, EventArgs e)
 45         {
 46             adminMan man1 = new adminMan(2, "大潤發養豬場");
 47             this.rtbMsgInfo.Text = man1.Msg;
 48         }
 49     }
 50 
 51 
 52     public class adminMan:feedPigFactory
 53     {
 54         private string _msg;
 55 
 56         public string Msg
 57         {
 58             get { return _msg; }
 59             set { _msg = value; }
 60         }
 61 
 62         public adminMan(int funId,string factoryname)
 63         {
 64             base.FactoryName = factoryname;
 65             robot robot1 = null;
 66             switch (funId)
 67             {
 68                 case 1:     //喂食
 69                     robot1= new robot();
 70                     IList<Ipig> list1=new List<Ipig>();
 71                     list1.Add(new dbPig(1));
 72                     list1.Add(new dbPig(2));
 73                     list1.Add(new dbPig(3));
 74                     robot1.transformation(new feedPig(list1));
 75                     this._msg = robot1.work();
 76                     break;
 77                 case 2:     //清潔
 78                     robot1= new robot();
 79                     robot1.transformation(new clean());
 80                     this._msg= robot1.work();
 81                     break;
 82                 default:
 83                     break;
 84             }
 85         }
 86 
 87 
 88     }
 89 
 90 
 91     public interface IfunInterface
 92     {
 93         string work();
 94     }
 95 
 96     public class robot
 97     {
 98         private IfunInterface _robotFun;
 99 
100         public IfunInterface RobotFun
101         {
102             get { return _robotFun; }
103             set { _robotFun = value; }
104         }
105 
106         public void transformation(IfunInterface robotFun)
107         {
108             this._robotFun = robotFun;
109         }
110         public string work()
111         {
112            return this._robotFun.work();
113         }
114     }
115 
116 
117 
118 
119 
120     public class clean : IfunInterface
121     {
122         public string work()
123         {
124             return "正在打掃清潔..."+Environment.NewLine;
125         }
126     }
127 
128     public class feedPig : IfunInterface
129     {
130         IList<Ipig> pigList = new List<Ipig>();
131 
132         public feedPig(IList<Ipig> plist)
133         {
134             foreach (Ipig m in plist)
135                 pigList.Add(m);
136         }
137 
138         public feedPig()
139         {
140         }
141 
142         public void Attack(Ipig pig)
143         {
144             pigList.Add(pig);
145         }
146 
147         public string work()
148         {
149             string msgstr = string.Empty;
150             foreach (Ipig m in pigList)
151             {
152                 msgstr += m.eat() + Environment.NewLine;
153             }
154 
155             return string.Format("{0}{1}{2}",
156                 "大潤發養豬場" + Environment.NewLine,
157                 "喂豬機器人開始工作...." + Environment.NewLine + Environment.NewLine,
158                 msgstr);
159         }
160     }
161 
162 
163 
164     public abstract class feedPigFactory
165     {
166         private string _factoryName;
167 
168         public string FactoryName
169         {
170             get { return _factoryName; }
171             set { _factoryName = value; }
172         }
173 
174     }
175 
176 
177 
178     public interface Ipig
179     {
180 
181         int PigIndex
182         {
183             get;
184             set;
185         }
186 
187         string eat();
188 
189     }
190 
191 
192     public class cbPig : Ipig
193     {
194         private int _pigIndex;
195 
196         public int PigIndex
197         {
198             get { return _pigIndex; }
199             set { _pigIndex = value; }
200         }
201         public cbPig(int pignum)
202         {
203             this._pigIndex = pignum;
204         }
205 
206         public string eat()
207         {
208             return string.Format("{0}[{1}]開始吃.", "長白豬", _pigIndex);
209         }
210     }
211 
212 
213 
214     public class dbPig : Ipig
215     {
216         private int _pigIndex;
217 
218         public int PigIndex
219         {
220             get { return _pigIndex; }
221             set { _pigIndex = value; }
222         }
223         public dbPig(int pignum)
224         {
225             this._pigIndex = pignum;
226         }
227 
228         public string eat()
229         {
230             return string.Format("{0}[{1}]開始吃.", "大白豬", _pigIndex);
231         }
232     }
233 
234 
235 
236 }

 

運行結果如上圖所示, 老闆可以下達指令在喂食和清潔機器人之間切換了.

 

代碼說明:

 在這裡,喂豬機器人類是把原來直接調用Work() 喂食方法, 變成了先由transformation()指定功能類型, 再來執行Work().

public class robot
    {
        private IfunInterface _robotFun;

        public IfunInterface RobotFun
        {
            get { return _robotFun; }
            set { _robotFun = value; }
        }

        public void transformation(IfunInterface robotFun)
        {
            this._robotFun = robotFun;
        }
        public string work()
        {
           return this._robotFun.work();
        }
    }

而功能類型就是個IfunInterface介面, 而clearn(打掃清潔功能), feedPig(喂豬功能), 都是承繼這個介面的.

public interface IfunInterface
    {
        string work();
    }
public class clean : IfunInterface
public class feedPig : IfunInterface

最後利用工廠方法, 決定了機器人在喂食,還是清潔兩種功能之間切換.

 public adminMan(int funId,string factoryname)
        {
            base.FactoryName = factoryname;
            robot robot1 = null;
            switch (funId)
            {
                case 1:     //喂食
                    robot1= new robot();
                    IList<Ipig> list1=new List<Ipig>();
                    list1.Add(new dbPig(1));
                    list1.Add(new dbPig(2));
                    list1.Add(new dbPig(3));
                    robot1.transformation(new feedPig(list1));
                    this._msg = robot1.work();
                    break;
                case 2:     //清潔
                    robot1= new robot();
                    robot1.transformation(new clean());
                    this._msg= robot1.work();
                    break;
                default:
                    break;
            }
        }

實際上我們是聚合IfunInterface這個抽象介面,即通過指向介面類的引用來訪問對象, 這種實現方法其實是綜合了聚合與繼承兩種機制的方式

 

此後,當我們添加一個新的機器人種類(如獸醫機器人)時,只需要添加一個獸醫功能的派生類,老闆就可以根據自己的需要,在任何時刻命令機器人在三個種類之間隨意變形。可以看出,添加一個機器人類型時,需要改動的代碼都在系統外部,系統內已有的代碼不需要發生變化。這裡的聚合機制使我們很好地滿足了開閉原則。

總之,繼承和聚合是兩種各不相同也各有優缺點的機制:

  • 繼承反映的是類之間"……是一個……"這樣的關係,它在編譯期間靜態定義。繼承的優點是使用起來比較簡單(因為面向對象的語言直接支持繼承機制),對設計

人員來說比較容易理解。但繼承也有缺點:

首先,你不能在運行期間改變繼承樹的結構,因為繼承是在編譯期間定義的:

其次,基類中往往定義了部分的實現,基類的實現暴露給派生類後,繼承機制就會破壞數據和操作的封裝,使派生類對基類產生較強的依賴O

 

  • 聚合反映的是類之間"有-個……"或"……包含一個……"的關係,它是在運行期間動態定義的,因此,被聚合對象的類型可以很容易地在運行期間發生變化,只要我們保證它們的介面相同,滿足完全替換原則即可。而且,使用聚合可以更好地封裝對象,使每一個類集中在單個職能上,類的繼承層次也會保持較小的規模,不會造成類數量的爆炸。聚合的缺點是它並不是面向對象語言直接支持的一個特性,用戶必須編寫一些代碼來完成聚合功能。例如,上面機器人類中的"工作"方法就必須把消息轉發給內部聚合的功能對象,即調用功能對象的"工作"方法。被聚合對象的介面必須遵從聚合類的要求,這種消息轉發的方式又被稱為"委托( Delegation ) "。一般來說,聚合的結構比繼承更難理解一些。

從上面的分析可以看出,聚合在某些方面比繼承更為優越。但我們強調聚合的作用絕不是否定繼承的優點。使用聚合時,我們必須遵循針對介面編程的設計原則,不能聚合某一個具體的派生類對象,而應該聚合該類的抽象介面,即通過指向介面類的引用或指針來訪問對象----這種實現方法其實是綜合了聚合與繼承兩種機制的方式。


由此,我們可以總結出設計模式的第三個核心設計原則
繼承反映的是類之間的"……是一個…"的關係,聚合反映的是類之間"…有一個……"或包含一個……"的關係。在不違反這個關係前提下,應該
優先使用聚合而不是繼承, 同時,聚合也必須和介面及相關的繼承結構協同使用。

 

全文完.

 

本文源代碼下載

包括面向對象養豬廠的各種版本實現代碼(C#示例), 和VS2010繪製的UML類圖.

 


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

-Advertisement-
Play Games
更多相關文章
  • 一、轉向(Forward) 1.要點說明 轉向是通過RequestDispatcher對象的forward()方法來實現的。RequestDispatcher可以通過HttpServletRequest的getRequestDispatcher()方法獲得。getRequestDispatcher( ...
  • Go語言是Google內部主推的語言,它作為一門全新的靜態類型開發語言,與當前的開發語言相比具有許多令人興奮不已的新特性。專門針對多處理器系統的應用程式編程進行了優化,使用go語言完全可以媲美c、c++的速度,而且更加安全、簡潔,支持並行進程。 以下是go語言的主要特性: 1、自動垃圾回收 2、更豐 ...
  • --> Poker類用於存入54張撲克牌 --> Player類對玩家進行發牌和顯示處理(寫完我就後悔了,J,Q,K,A,2的排序太low了..package com.dragon.java.hwddz; --> 寫完發現邏輯是不是有問題啊... ...
  • 定義: 單來源調用指一個類的生成工作只能由特定類來執行。 eg李寧牌鞋子只能由李寧專賣店生產 這個問題歸結起來,也就是說在工廠模式中,指定的產品類只能通過具體的特定工廠類來生成,而不能自己new出來或者通過其他類生成。 具體的,我們就在代碼實現中進行說明瞭。 這裡我們來一步一步分析。 首先,一個類實 ...
  • 昨天跟大家分享了Hibernate中單向的一對多、單向多對一、雙向一對多的映射關係,今天跟大家分享下在Hibernate中雙向的多對多的映射關係 這次我們以項目和員工舉個慄子,因為大家可以想象得到,在真實的環境下,一個項目肯定是對應著多個員工的,這毫無疑問, 那麼同時,一個比較牛員工也能同時參與多個 ...
  • 接觸Hibernate也有一小段的時間了,愈發的覺得Hibernate是個神奇的東西,為什麼這麼說呢?因為你可以不懂一行sql,直接面向對象,就可以將數據直接保存到資料庫去!! 你還可以保存一個對象,然後一次性的將與它相關的所有數據保存到資料庫,比如說,你只需要保存班級對象,就可以將該班級信息和該班 ...
  • "封裝、"多態"、"繼承"。 "單一職責原則"、"開放封閉原則"、"里氏替換原則"、"依賴倒置原則"、"介面分離原則"。 低耦合與高內聚 ...
  • 首先,我看的是Nop 3.80,最新版 百度資料很多,Nop用到的主要的技術有: 1、Mvc,最新版用的是 5.2.3.0 2、entity framework 3、autofac 4、插件化 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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...