相關閱讀 |
>>> 創業先鋒 眾人拾柴火焰高 >>> | 簡體 傳統 |
應園友提議,本篇博將幫助大家解決“針對接口編程”這一疑惑。而我所講的例子將從上一篇設計引導---一個鴨子游戲引發的設計(多態,繼承,抽象,接口,策略者模式)的案例中,延伸下來,讓大家更容易閱讀。
上篇中有提到鴨子游戲。現在,假設那個鴨子游戲火了,火遍全球~~~公司大佬們因為這個游戲賺的盆滿缽滿,像憤怒的小鳥一樣:
現在公司下一步計劃!打造一個以游戲中鴨子個體為模型的玩具工廠!o(∩_∩)o
就像憤怒的小鳥毛絨玩具一樣~用這個比喻,大家應該會很簡單的想象模擬場景。
下面來進入正題!漸進式描述,讓大家有個進階的梯度( ̄︶ ̄)↗ 。
(oh fuck 下面這些內容 是我第二次寫!第一次寫的沒保存,IE死掉了!內牛滿面。。。。)
看看玩具訂單:
1 public DuckToy OrderToy() 2 { 3 DuckToy ducktoy = new DuckToy(); 4 5 ducktoy.Prepare();//準備 6 ducktoy.Create();//開始制作 7 ducktoy.Package();//包裝 8 ducktoy.freight();//運送 9 10 return ducktoy; 11 }
這個方法很簡單,你僅僅只要關注new DuckToy(),針對一種鴨子玩具的生產!你還需要更多的鴨子模型!僅僅一種玩具,不足以打開市場!
下面改進一下:
1 public DuckToy OrderToy(string type) 2 { 3 DuckToy ducktoy; 4 5 if (type.Equals("MallarDuck")) 6 { 7 ducktoy = new MallarDuck();//綠頭鴨 8 } 9 else if (type.Equals("RedHaedDuck")) 10 { 11 ducktoy = new RedHaedDuck();//紅頭鴨 12 } 13 else if (type.Equals("DecoyDuck")) 14 { 15 ducktoy = new DecoyDuck();//誘餌鴨 16 } 17 18 //還有更多的鴨子模型哦~ 19 //... 20 21 ducktoy.Prepare();//準備 22 23 ducktoy.Package();//包裝 24 ducktoy.freight();//運送 25 26 return ducktoy; 27 }
看到我最后實例化的注釋就知道,這根本不行,這種方式不是我們想要的。維護極度煩人。
把創建對象提取出來也許會不錯,可以把這個新對象稱作“工廠”呢:
1 public class SimpleToyFactory 2 { 3 public DuckToy CreateToy(string type) 4 { 5 DuckToy ducktoy = null; 6 if (type.Equals("MallarDuck")) 7 { 8 ducktoy = new MallarDuck();//綠頭鴨 9 } 10 else if (type.Equals("RedHaedDuck")) 11 { 12 ducktoy = new RedHaedDuck();//紅頭鴨 13 } 14 else if (type.Equals("DecoyDuck")) 15 { 16 ducktoy = new DecoyDuck();//誘餌鴨 17 } 18 19 return ducktoy; 20 } 21 }
就把代碼搬了一個地方,原來的問題還是沒有解決,繁瑣的問題依然存在。但這個移動也不能說完全沒好處,目前這個類只有一個OrderToy方法是它的客戶,以后還可以有很多的功能類,來用到這個“工廠”,比如獲取價錢或者描述,或者玩具列表....這個提取出來正是因為我們要把它從客戶的代碼中刪除。
這個類也可以用靜態方法來定義,常稱為靜態工廠,方便是方便,就是不能再繼承來改變方法的行為了。
好了,現在“工廠”方法做出來了,要重寫一下DuckToysStore了,OrderToy這個是DuckToysStore的,看你看完整的代碼,你能理解的:
1 public class DuckToyStore 2 { 3 SimpleToyFactory factory; 4 public DuckToyStore(SimpleToyFactory factory) 5 { 6 this.factory = factory; 7 } 8 9 public DuckToy OrderToy(string type) 10 { 11 DuckToy ducktoy; 12 13 ducktoy = factory.CreateToy(type); 14 15 ducktoy.Prepare();//準備 16 17 ducktoy.Package();//包裝 18 ducktoy.freight();//運送 19 20 return ducktoy; 21 } 22 }
當然,這只是OrderToy部分代碼,這看起來舒服多了~代碼中已經看不到new了,這從某種程度上來說,我們完成了一部分接口編程的知識了!
這個“工廠“已經做完了,就提取了一下代碼,我們可以把這個方法當作一種編程習慣,對,它不是工廠模式,它僅僅是一種較好的習慣。
正因為有這個的存在,許多開發人員都會誤認為這就是工廠模式了。在下面我會介紹工廠模式并給予區別!
看個圖來輕松一下,理下剛才的關系:
通過簡單工廠的介紹,還沒有完哦,工廠繼續:
玩具店,開的不錯,有很多加盟商了,隨之而來的是地域文化差異,不同的地方,玩具銷量也不同,比如在海邊,小鴨玩具就不能再做毛絨了, 那銷量肯定不好,小鴨游泳圈,小鴨船,會比毛絨玩具賣的好o(∩_∩)o ,更多想象只局限你思維啦~
針對這個問題,玩具加盟商需要有自己的玩具類型,它們可以使用游戲公司的小鴨模型(專利化,一般沒有授權的,模仿小鴨模型會被追究的),但可以用不同的材質,一般的是毛絨,但是在海邊,塑料的才好賣,還可以有氣球樣式...。
繼續移植簡單工廠的做法:
1 BeiJingStyleFactory bjFactory = new BeiJingStyleFactory(); 2 DuckToyStore bjStore = new DuckToyStore(bjFactory); 3 bjStore.OrderToy("MallarDuck");
這段代碼意思是:有一個北京樣式商店,它有自己的工廠,我要下訂單的話,只要讓DuckToyStore構造一個北京樣式商店,來處理我想要的”MallarDuck“,我就能得到一款,正在北京商店賣的“MallarDuck”。
當然,海南的樣式(HainanStyleFactory)商店,也可以這么做,代碼差不多。
就是這代碼差不多!讓各地商家都捆綁在了你的創建模型上面了,你管不了他們做自己的特色,現在要做個框架。讓各地的玩具廠商,做自己的特色。
玩具變幻無窮~先來理一下關系。
玩具廠商,就好比一個工廠,他生產各種各樣的玩具,但他想生產別人的專利玩具模型,就需要加盟,獲得持有專利公司的授權,這個很容易理解吧,然后他才可以名正言順的生產“鴨子玩具”。
一個玩具工廠,他可以生產各種各樣的玩具,就意味著他可以自己制造自己的產品,他對于生產的玩具,有自己的理念,有自己的方法和創新:
每種玩具,可以是相同的樣式,但需要不同的顏色去裝飾,以及配上什么裝飾品,這是玩具廠商的事,比如給專利“鴨子模型”配備一個小背包?裝飾一朵小花?小花的顏色設置,這些細節問題,專利公司他管理不了這么多,更多的制作權,還是在玩具工廠,玩具公司!
公司需要一個自主的創建玩具的方法!現在重新設計一下,做個更有彈性的設計:
1 public abstract class DuckToyStore 2 { 3 4 public DuckToy OrderToy(string type) 5 { 6 DuckToy ducktoy; 7 8 //ducktoy = factory.CreateToy(type); 9 ducktoy = CreateToy(type);//這里沒有new哦 10 11 ducktoy.Prepare();//準備 12 ducktoy.Create();//開始制作 13 ducktoy.Package();//包裝 14 ducktoy.freight();//運送 15 16 return ducktoy; 17 } 18 19 public abstract DuckToy CreateToy(string type);//授予子類去new 20 }
把這個做成一個抽象類,讓繼承的子類去實現CreateToy(制造玩具),看個圖:
OrderToy()現在已經被分離出來了,它不知道哪些實際的具體類參與進來了,專業點,這就是解耦(decouple)
來具體實現一下:
實現之前,先做好商店內的具體模型:
//為了方便,放一起 class BeiJingStyleMallarDuck:DuckToy { public BeiJingStyleMallarDuck() { Console.WriteLine("初始化..");//傳統毛絨玩具 name = "北京綠頭鴨"; material = "傳統毛絨玩具"; color = "白色"; } } class BeiJingStyleRedHeadDuck : DuckToy { public BeiJingStyleRedHeadDuck() { Console.WriteLine("初始化..."); name = "北京紅頭鴨"; material = "傳統毛絨玩具"; color = "綠色"; } } class HainanStyleMallarDuck : DuckToy { public HainanStyleMallarDuck() { Console.WriteLine("初始化...");//靠近海邊的用塑料防水材質 name = "海南綠頭鴨"; material = "防水橡膠材質"; color = "白色"; } } class HainanStyleRedHeadDuck:DuckToy{ public HainanStyleRedHeadDuck() { Console.WriteLine("初始化..."); name = "海南紅頭鴨"; material = "防水橡膠材質"; color = "綠色"; } }
現在可以做商店啦,雖然代碼有點多,但是都是相同的概念,在看代碼的時候,要構造好這些相互之間的關系:
1 class BeiJingStyleStore: DuckToyStore 2 {//北京 3 public override DuckToy CreateToy(string item) 4 { 5 if (item.Equals("MallarDuck")) 6 { 7 return new BeiJingStyleMallarDuck(); 8 } 9 else if (item.Equals("RedHeadDuck")) 10 { 11 return new BeiJingStyleRedHeadDuck(); 12 } 13 else 14 { 15 return null; 16 } 17 } 18 } 19 class HaiNanStyleStore:DuckToyStore 20 {//海南 21 public override DuckToy CreateToy(string item) 22 { 23 if (item.Equals("MallarDuck")) 24 { 25 return new HainanStyleMallarDuck(); 26 } 27 else if (item.Equals("RedHeadDuck")) 28 { 29 return new HainanStyleRedHeadDuck(); 30 } 31 else 32 { 33 return null; 34 } 35 } 36 }
是不是很期待程序的結果??Main登場┈━═☆:
1 //親測可用哦o(∩_∩) 2 static void Main(string[] args) 3 { 4 DuckToyStore bjStyleStore = new BeiJingStyleStore();//創建商店 5 bjStyleStore.OrderToy("MallarDuck");//下單 6 7 }
代碼有沒有覺得很是簡單呢?
bjStyleStore.OrderToy("MallarDuck"); --- 這段代碼的參數也許會引起不好的爭論,為了方便,我就寫個String了,用個Enum來集合玩具樣式是個不錯的方法。
結果:
有點感觸嗎?例子都講完了~ 看下詳細的定義,現在你的頭腦里應該會有兩個結構關系,像這樣:
一個創建類,一個產品類,工廠模式通過子類決定該創建的對象是什么,來達到將對象創建的過程封裝的目的。
正式定義,工廠方法模式:
定義了一個創建對象的接口,但由子類決定要實列化的類是哪一個。
工廠方法讓類把實例化推遲到子類。
工廠方法讓子類決定實列化哪一個類,不需要知道實際創建的產品是哪一個。
選擇了使用哪個子類,自然就決定了實際創建的產品是什么。現在可以隨意的擴展你的工廠了,比如加一個上海的商店,自定義自己的玩具模型~都很輕松啦。
現在說說簡單工廠和工廠方法,大家現在應該覺得,子類看起來很像簡單工廠。
簡單工廠把全部的事情,在一個地方都處理完了,而工廠方法卻是創建一個框架,讓子類決定要如何實現。
簡單工廠可以將對象封裝起來,但是簡單工廠不具備工廠方法的彈性,因為簡單工廠不能改變正在創建的產品。
例子的前部分從依賴具體對象,講到依賴接口實例化對象,具體設計原則如下:
要依賴抽象,不要依賴具體
對于這一原則,怎么去判斷,提及一個正式的名稱去區別:依賴倒置原則。
什么是依賴?比如簡單工廠~典型的依賴。
看看倒置:
倒置指的是和一般面向對象設計的思考方式完全相反。不好理解?嘿,絕對讓你懂。
問:“現在你需要實現一個上海玩具店,你第一件事情想到什么?”
小花:“我要自己創建自己的風格樣式玩具!比如:咧嘴的綠頭鴨,憤怒的綠頭鴨...”
問:“現在你的思維正在順勢從頂端思考,現在倒置一下,先考慮具體的模型玩具!”
小花:“我的小鴨玩具們,都要共享一個鴨子接口。”
問:“Yes,你想到一個抽象的鴨子接口,那么你再想想,如何設計玩具店?”
小花:“噢!我現在要關心的是玩具店的樣式,而不是咧嘴的綠頭鴨(具體的模型)。”
---總結:
你要做具體的模型,就必須靠一個工廠將這些具體模型抽象出來,然后各種具體的模型都會依賴一個抽象(鴨子接口),而你的玩具商店也會依賴這個抽象接口。
現在不僅僅倒置了一個商店依賴具體類的設計,而且也倒置了你的思考方式,下面有幾點指導意見,幫助你避免在面向對象設計中違反依賴倒置原則:
抽象工廠模式:
提供一個接口,用于創建相關或依賴對象的家族,而不需要明確置頂具體類。
注意這個抽象工廠模式和工廠方法不是一個概念,他們有不同的定義,再次提醒,他們不是同一個模式,但是又有很多交織在一起的枝節。
抽象工廠的方法經常以工廠方法的方式實現。抽象工廠的任務是定義一個負責創建一組產品的接口。
這個接口內的每個方法都負責創建一個具體的產品,還沒完,然后再利用實現抽象工廠的子類來提供這些具體的做法。
也就是說,抽象工廠中利用工廠方法實現生產的具體做法。
來看看抽象工廠的代碼吧,畢竟看了這么多知識了,比較比較代碼也是能促進學習的。
我偷懶了,只完成了一個實例,目的很簡單,想讓大家看到抽象工廠的實現:
先看具體DuckToy,修改版:
1 public abstract class DuckToy 2 { 3 //都是封裝好的類型 4 public FactroyName name; 5 public FactroyMaterial material; 6 public FactroyColor color; 7 8 public abstract void Prepare(); //準備工作 現在是抽象方法 9 10 public void Create() 11 { 12 Console.WriteLine("正在生產...(預計30分鐘)"); 13 } 14 15 public void Package() 16 { 17 Console.WriteLine("正在包裝...(預計2分鐘)"); 18 } 19 public void Freight() 20 { 21 Console.WriteLine("正在運送..."); 22 } 23 24 }
再看封裝類型的字段:
//開始會難以理解。 - -。既然是工廠,那么工廠就有統一的標準,不允許用戶自行修改 public class FactroyMaterial { public FactroyMaterial()//為了簡單,我在這直接用構造函數,其實你還可以自己定義自己的方法,下面的幾個類都可以自己擴展 { Console.WriteLine("材料:毛絨"); } } public class FactroyName { public FactroyName() { Console.WriteLine("名字:北京綠頭鴨"); } } public class FactroyColor { public FactroyColor() { Console.WriteLine("顏色:白色"); } }
上面的代碼是北京商店特有的,為什么這么說? 看看這個:
1 public class BJToyStroeFactory: ToysFactory 2 {//北京商店特有的材料 名字 顏色 3 public FactroyMaterial ReturnMaterial() 4 { 5 return new FactroyMaterial(); 6 } 7 public FactroyName ReturnName() 8 { 9 return new FactroyName(); 10 } 11 public FactroyColor ReturnColor() 12 { 13 return new FactroyColor(); 14 } 15 }
現在又出來一個ToysFactory,接著就是抽象工廠的接口啦:
public interface ToysFactory//通用接口方法 { FactroyMaterial ReturnMaterial(); FactroyName ReturnName(); FactroyColor ReturnColor(); }
底層都寫好了,好好好,現在要看具體的鴨子模型!
1 class BeiJingStyleMallarDuck:DuckToy 2 { 3 ToysFactory toysfactory; 4 public BeiJingStyleMallarDuck(ToysFactory tosyfactory) 5 { 6 this.toysfactory = tosyfactory; 7 } 8 public override void Prepare()//實現 9 { 10 Console.WriteLine("開始準備..."); 11 material = toysfactory.ReturnMaterial(); 12 name = toysfactory.ReturnName(); 13 color = toysfactory.ReturnColor(); 14 } 15 }
繼承下來的封裝類型,通過Prepare,從工廠獲取實物的信息。別著急,我們再向上一層。
1 //重寫了 BeiJingStyleStore 2 class BeiJingStyleStore: DuckToyStore 3 { 4 DuckToy toy = null; 5 ToysFactory toysfactory = new BJToyStroeFactory(); 6 public override DuckToy CreateToy(string item) 7 { 8 if (item.Equals("MallarDuck")) 9 { 10 toy = new BeiJingStyleMallarDuck(toysfactory); 11 return toy; 12 }//偷懶了~~~~就寫一個toy 13 else 14 { 15 return null; 16 } 17 } 18 }
都改了這么多了,看看Main要改什么嗎?
1 static void Main(string[] args) 2 { 3 DuckToyStore bjStyleStore = new BeiJingStyleStore(); 4 bjStyleStore.OrderToy("MallarDuck"); 5 } 6 //什么都沒改^_^
改了這么多,Main都不需要改,OrderToy所在的DuckToyStore也不用改,但是它的流程已經脫胎換股了。
結果差點忘記貼:
我寫了這么多不僅僅是一個工廠模式,還有一個抽象工廠模式。
看看下面兩張圖比較一下吧,這兩種模式的結構
-----------------------------------------------------------------------------------------------------------
下面是工廠方法:
工廠是很有威力的技巧,幫助我們針對抽象編程,而不需要針對具體編程~
原例子---《Head First 設計模式》--工廠模式:披薩店。
對設計有興趣的朋友,強烈推薦拿下此書。磨刀不誤砍柴工~~~^_^
肅 2013-08-31 20:16:18
稱謂:
内容: