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

时间:2021-07-01 07:45:33

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

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

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

  现在公司下一步计划!打造一个以游戏中鸭子个体为模型的玩具工厂!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了,这从某种程度上来说,我们完成了一部分接口编程的知识了!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  一个玩具工厂,他可以生产各种各样的玩具,就意味着他可以自己制造自己的产品,他对于生产的玩具,有自己的理念,有自己的方法和创新:                      设计引导---不要再盲目的new了!你要学着针对接口编程!(具体方法,Factory,Abstract Factory)

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

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

 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(制造玩具),看个图:

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

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来集合玩具样式是个不错的方法。

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

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

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

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

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

 

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

  工厂方法让类把实例化推迟到子类。

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

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

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

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

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

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

要依赖抽象,不要依赖具体

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

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

看看倒置:

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

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

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

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

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

问:“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也不用改,但是它的流程已经脱胎换股了。

结果差点忘记贴:

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

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

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

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

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

下面是工厂方法:

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

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

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

对设计有兴趣的朋友,强烈推荐拿下此书。磨刀不误砍柴工~~~^_^