应园友提议,本篇博将帮助大家解决“针对接口编程”这一疑惑。而我所讲的例子将从上一篇设计引导---一个鸭子游戏引发的设计(多态,继承,抽象,接口,策略者模式)的案例中,延伸下来,让大家更容易阅读。
上篇中有提到鸭子游戏。现在,假设那个鸭子游戏火了,火遍全球~~~公司大佬们因为这个游戏赚的盆满钵满,像愤怒的小鸟一样:
现在公司下一步计划!打造一个以游戏中鸭子个体为模型的玩具工厂!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,你想到一个抽象的鸭子接口,那么你再想想,如何设计玩具店?”
小花:“噢!我现在要关心的是玩具店的样式,而不是咧嘴的绿头鸭(具体的模型)。”
---总结:
你要做具体的模型,就必须靠一个工厂将这些具体模型抽象出来,然后各种具体的模型都会依赖一个抽象(鸭子接口),而你的玩具商店也会依赖这个抽象接口。
现在不仅仅倒置了一个商店依赖具体类的设计,而且也倒置了你的思考方式,下面有几点指导意见,帮助你避免在面向对象设计中违反依赖倒置原则:
- 变量不能持有具体类的引用,就像订单方法代码中,你看不到new一样。
- 不要让派生自具体类,要派生就派生抽象类abstract
- 不要覆盖基类中已实现的方法,除非你要覆盖的是比较特殊的一部分代码。
抽象工厂模式:
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确置顶具体类。
注意这个抽象工厂模式和工厂方法不是一个概念,他们有不同的定义,再次提醒,他们不是同一个模式,但是又有很多交织在一起的枝节。
抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口。
这个接口内的每个方法都负责创建一个具体的产品,还没完,然后再利用实现抽象工厂的子类来提供这些具体的做法。
也就是说,抽象工厂中利用工厂方法实现生产的具体做法。
来看看抽象工厂的代码吧,毕竟看了这么多知识了,比较比较代码也是能促进学习的。
我偷懒了,只完成了一个实例,目的很简单,想让大家看到抽象工厂的实现:
先看具体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 设计模式》--工厂模式:披萨店。
对设计有兴趣的朋友,强烈推荐拿下此书。磨刀不误砍柴工~~~^_^