您的位置:首页 > 编程语言

设计引导---不要再盲目的new了!你要学着针对接口编程!(具体方法,Factory,Abstract Factory)

2013-01-06 09:35 645 查看
 应园友提议,本篇博将帮助大家解决“针对接口编程”这一疑惑。而我所讲的例子将从上一篇设计引导---一个鸭子游戏引发的设计(多态,继承,抽象,接口,策略者模式)的案例中,延伸下来,让大家更容易阅读。

  上篇中有提到鸭子游戏。现在,假设那个鸭子游戏火了,火遍全球~~~公司大佬们因为这个游戏赚的盆满钵满,像愤怒的小鸟一样:

                    


  现在公司下一步计划!打造一个以游戏中鸭子个体为模型的玩具工厂!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(),针对一种鸭子玩具的生产!你还需要更多的鸭子模型!仅仅一种玩具,不足以打开市场!

下面改进一下:

public DuckToy OrderToy(string type)
{
DuckToy ducktoy;

if (type.Equals("MallarDuck"))
{
ducktoy = new MallarDuck();//绿头鸭
}
else if (type.Equals("RedHaedDuck"))
{
ducktoy = new RedHaedDuck();//红头鸭
}
else if (type.Equals("DecoyDuck"))
{
ducktoy = new DecoyDuck();//诱饵鸭
}

//还有更多的鸭子模型哦~
//...

ducktoy.Prepare();//准备

ducktoy.Package();//包装
ducktoy.freight();//运送

return ducktoy;
}


看到我最后实例化的注释就知道,这根本不行,这种方式不是我们想要的。维护极度烦人。

把创建对象提取出来也许会不错,可以把这个新对象称作“工厂”呢:

public class SimpleToyFactory
{
public DuckToy CreateToy(string type)
{
DuckToy ducktoy = null;
if (type.Equals("MallarDuck"))
{
ducktoy = new MallarDuck();//绿头鸭
}
else if (type.Equals("RedHaedDuck"))
{
ducktoy = new RedHaedDuck();//红头鸭
}
else if (type.Equals("DecoyDuck"))
{
ducktoy = new DecoyDuck();//诱饵鸭
}

return ducktoy;
}
}


就把代码搬了一个地方,原来的问题还是没有解决,繁琐的问题依然存在。但这个移动也不能说完全没好处,目前这个类只有一个OrderToy方法是它的客户,以后还可以有很多的功能类,来用到这个“工厂”,比如获取价钱或者描述,或者玩具列表....这个提取出来正是因为我们要把它从客户的代码中删除。

  这个类也可以用静态方法来定义,常称为静态工厂,方便是方便,就是不能再继承来改变方法的行为了。

  好了,现在“工厂”方法做出来了,要重写一下DuckToysStore了,OrderToy这个是DuckToysStore的,看你看完整的代码,你能理解的:

public class DuckToyStore
{
SimpleToyFactory factory;
public DuckToyStore(SimpleToyFactory factory)
{
this.factory = factory;
}

public DuckToy OrderToy(string type)
{
DuckToy ducktoy;

ducktoy = factory.CreateToy(type);

ducktoy.Prepare();//准备

ducktoy.Package();//包装
ducktoy.freight();//运送

return ducktoy;
}
}


当然,这只是OrderToy部分代码,这看起来舒服多了~代码中已经看不到new了,这从某种程度上来说,我们完成了一部分接口编程的知识了!

这个“工厂“已经做完了,就提取了一下代码,我们可以把这个方法当作一种编程习惯,对,它不是工厂模式,它仅仅是一种较好的习惯。

  正因为有这个的存在,许多开发人员都会误认为这就是工厂模式了。在下面我会介绍工厂模式并给予区别!

看个图来轻松一下,理下刚才的关系:



通过简单工厂的介绍,还没有完哦,工厂继续:

  玩具店,开的不错,有很多加盟商了,随之而来的是地域文化差异,不同的地方,玩具销量也不同,比如在海边,小鸭玩具就不能再做毛绒了, 那销量肯定不好,小鸭游泳圈,小鸭船,会比毛绒玩具卖的好o(∩_∩)o ,更多想象只局限你思维啦~

  针对这个问题,玩具加盟商需要有自己的玩具类型,它们可以使用游戏公司的小鸭模型(专利化,一般没有授权的,模仿小鸭模型会被追究的),但可以用不同的材质,一般的是毛绒,但是在海边,塑料的才好卖,还可以有气球样式...。

继续移植简单工厂的做法:

BeiJingStyleFactory bjFactory = new BeiJingStyleFactory();
DuckToyStore bjStore = new DuckToyStore(bjFactory);
bjStore.OrderToy("MallarDuck");


这段代码意思是:有一个北京样式商店,它有自己的工厂,我要下订单的话,只要让DuckToyStore构造一个北京样式商店,来处理我想要的”MallarDuck“,我就能得到一款,正在北京商店卖的“MallarDuck”。

当然,海南的样式(HainanStyleFactory)商店,也可以这么做,代码差不多。

就是这代码差不多!让各地商家都捆绑在了你的创建模型上面了,你管不了他们做自己的特色,现在要做个框架。让各地的玩具厂商,做自己的特色。

玩具变幻无穷~先来理一下关系。

  玩具厂商,就好比一个工厂,他生产各种各样的玩具,但他想生产别人的专利玩具模型,就需要加盟,获得持有专利公司的授权,这个很容易理解吧,然后他才可以名正言顺的生产“鸭子玩具”。

  一个玩具工厂,他可以生产各种各样的玩具,就意味着他可以自己制造自己的产品,他对于生产的玩具,有自己的理念,有自己的方法和创新:      


每种玩具,可以是相同的样式,但需要不同的颜色去装饰,以及配上什么装饰品,这是玩具厂商的事,比如给专利“鸭子模型”配备一个小背包?装饰一朵小花?小花的颜色设置,这些细节问题,专利公司他管理不了这么多,更多的制作权,还是在玩具工厂,玩具公司!

  公司需要一个自主的创建玩具的方法!现在重新设计一下,做个更有弹性的设计:

public abstract class DuckToyStore
{

public DuckToy OrderToy(string type)
{
DuckToy ducktoy;

//ducktoy = factory.CreateToy(type);
ducktoy = CreateToy(type);//这里没有new哦

ducktoy.Prepare();//准备
ducktoy.Create();//开始制作
ducktoy.Package();//包装
ducktoy.freight();//运送

return ducktoy;
}

public abstract DuckToy CreateToy(string type);//授予子类去new
}


把这个做成一个抽象类,让继承的子类去实现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 = "绿色";
}
}


现在可以做商店啦,虽然代码有点多,但是都是相同的概念,在看代码的时候,要构造好这些相互之间的关系:

class BeiJingStyleStore: DuckToyStore
{//北京
public override DuckToy  CreateToy(string item)
{
if (item.Equals("MallarDuck"))
{
return new BeiJingStyleMallarDuck();
}
else if (item.Equals("RedHeadDuck"))
{
return new BeiJingStyleRedHeadDuck();
}
else
{
return null;
}
}
}
class HaiNanStyleStore:DuckToyStore
{//海南
public override DuckToy CreateToy(string item)
{
if (item.Equals("MallarDuck"))
{
return new HainanStyleMallarDuck();
}
else if (item.Equals("RedHeadDuck"))
{
return new HainanStyleRedHeadDuck();
}
else
{
return null;
}
}
}


是不是很期待程序的结果??Main登场┈━═☆:

//亲测可用哦o(∩_∩)
static void Main(string[] args)
{
DuckToyStore bjStyleStore = new BeiJingStyleStore();//创建商店
5             bjStyleStore.OrderToy("MallarDuck");//下单

}


代码有没有觉得很是简单呢?

bjStyleStore.OrderToy("MallarDuck"); --- 这段代码的参数也许会引起不好的争论,为了方便,我就写个String了,用个Enum来集合玩具样式是个不错的方法。

结果:



有点感触吗?例子都讲完了~ 看下详细的定义,现在你的头脑里应该会有两个结构关系,像这样:





一个创建类,一个产品类,工厂模式通过子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

正式定义,工厂方法模式:


[b]  定义了一个创建对象的接口,但由子类决定要实列化的类是哪一个。[/b]

[b]  工厂方法让类把实例化推迟到子类。[/b]


工厂方法让子类决定实列化哪一个类,不需要知道实际创建的产品是哪一个。

选择了使用哪个子类,自然就决定了实际创建的产品是什么。现在可以随意的扩展你的工厂了,比如加一个上海的商店,自定义自己的玩具模型~都很轻松啦。

现在说说简单工厂和工厂方法,大家现在应该觉得,子类看起来很像简单工厂。

简单工厂把全部的事情,在一个地方都处理完了,而工厂方法却是创建一个框架,让子类决定要如何实现。

简单工厂可以将对象封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能改变正在创建的产品。

例子的前部分从依赖具体对象,讲到依赖接口实例化对象,具体设计原则如下:


[b]要依赖抽象,不要依赖具体[/b]


对于这一原则,怎么去判断,提及一个正式的名称去区别:依赖倒置原则

什么是依赖?比如简单工厂~典型的依赖。

看看倒置:

倒置指的是和一般面向对象设计的思考方式完全相反。不好理解?嘿,绝对让你懂。

问:“现在你需要实现一个上海玩具店,你第一件事情想到什么?”

小花:“我要自己创建自己的风格样式玩具!比如:咧嘴的绿头鸭,愤怒的绿头鸭...”

问:“现在你的思维正在顺势从顶端思考,现在倒置一下,先考虑具体的模型玩具!”

小花:“我的小鸭玩具们,都要共享一个鸭子接口。”

问:“Yes,你想到一个抽象的鸭子接口,那么你再想想,如何设计玩具店?”

小花:“噢!我现在要关心的是玩具店的样式,而不是咧嘴的绿头鸭(具体的模型)。”

---总结:

  你要做具体的模型,就必须靠一个工厂将这些具体模型抽象出来,然后各种具体的模型都会依赖一个抽象(鸭子接口),而你的玩具商店也会依赖这个抽象接口。

现在不仅仅倒置了一个商店依赖具体类的设计,而且也倒置了你的思考方式,下面有几点指导意见,帮助你避免在面向对象设计中违反依赖倒置原则:

变量不能持有具体类的引用,就像订单方法代码中,你看不到new一样。

不要让派生自具体类,要派生就派生抽象类abstract

不要覆盖基类中已实现的方法,除非你要覆盖的是比较特殊的一部分代码。

抽象工厂模式:


  提供一个接口,用于创建相关或依赖对象的家族,而不需要明确置顶具体类。


注意这个抽象工厂模式和工厂方法不是一个概念,他们有不同的定义,再次提醒,他们不是同一个模式,但是又有很多交织在一起的枝节。

抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口。

这个接口内的每个方法都负责创建一个具体的产品,还没完,然后再利用实现抽象工厂的子类来提供这些具体的做法。

也就是说,抽象工厂中利用工厂方法实现生产的具体做法。

来看看抽象工厂的代码吧,毕竟看了这么多知识了,比较比较代码也是能促进学习的。

我偷懒了,只完成了一个实例,目的很简单,想让大家看到抽象工厂的实现:

先看具体DuckToy,修改版:

public abstract class DuckToy
{
//都是封装好的类型
public FactroyName name;
public FactroyMaterial material;
public FactroyColor color;

public abstract void Prepare(); //准备工作 现在是抽象方法

public void Create()
{
Console.WriteLine("正在生产...(预计30分钟)");
}

public void Package()
{
Console.WriteLine("正在包装...(预计2分钟)");
}
public void Freight()
{
Console.WriteLine("正在运送...");
}

}


再看封装类型的字段:

//开始会难以理解。 - -。既然是工厂,那么工厂就有统一的标准,不允许用户自行修改
public class FactroyMaterial
{
public FactroyMaterial()//为了简单,我在这直接用构造函数,其实你还可以自己定义自己的方法,下面的几个类都可以自己扩展
{
Console.WriteLine("材料:毛绒");
}
}
public class FactroyName
{
public FactroyName()
{
Console.WriteLine("名字:北京绿头鸭");
}
}
public class FactroyColor
{
public FactroyColor()
{
Console.WriteLine("颜色:白色");
}
}


上面的代码是北京商店特有的,为什么这么说? 看看这个:

public class BJToyStroeFactory: ToysFactory
{//北京商店特有的材料 名字 颜色
public FactroyMaterial ReturnMaterial()
{
return new FactroyMaterial();
}
public FactroyName ReturnName()
{
return new FactroyName();
}
public FactroyColor ReturnColor()
{
return new FactroyColor();
}
}


现在又出来一个ToysFactory,接着就是抽象工厂的接口啦:

public interface ToysFactory//通用接口方法
{
FactroyMaterial ReturnMaterial();
FactroyName ReturnName();
FactroyColor ReturnColor();
}


底层都写好了,好好好,现在要看具体的鸭子模型!

class BeiJingStyleMallarDuck:DuckToy
{
ToysFactory toysfactory;
public BeiJingStyleMallarDuck(ToysFactory tosyfactory)
{
this.toysfactory = tosyfactory;
}
public override void  Prepare()//实现
{
Console.WriteLine("开始准备...");
material = toysfactory.ReturnMaterial();
name = toysfactory.ReturnName();
color = toysfactory.ReturnColor();
}
}


继承下来的封装类型,通过Prepare,从工厂获取实物的信息。别着急,我们再向上一层。

//重写了 BeiJingStyleStore
class BeiJingStyleStore: DuckToyStore
{
DuckToy toy = null;
ToysFactory toysfactory = new BJToyStroeFactory();
public override DuckToy  CreateToy(string item)
{
if (item.Equals("MallarDuck"))
{
toy = new BeiJingStyleMallarDuck(toysfactory);
return toy;
}//偷懒了~~~~就写一个toy
13                 else
{
return null;
}
}
}


都改了这么多了,看看Main要改什么吗?

static void Main(string[] args)
{
DuckToyStore bjStyleStore = new BeiJingStyleStore();
bjStyleStore.OrderToy("MallarDuck");
}
//什么都没改^_^


改了这么多,Main都不需要改,OrderToy所在的DuckToyStore也不用改,但是它的流程已经脱胎换股了。

结果差点忘记贴:



我写了这么多不仅仅是一个工厂模式,还有一个抽象工厂模式。

看看下面两张图比较一下吧,这两种模式的结构



-----------------------------------------------------------------------------------------------------------

下面是工厂方法:





工厂是很有威力的技巧,帮助我们针对抽象编程,而不需要针对具体编程~

原例子---《Head First 设计模式》--工厂模式:披萨店。

对设计有兴趣的朋友,强烈推荐拿下此书。磨刀不误砍柴工~~~^_^
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐