设计模式——工厂方法模式

来源:互联网 发布:小译软件 编辑:程序博客网 时间:2024/05/07 05:30

最近在看《软件设计精要与模式(第2版)》,先从第6章《.NET中的工厂方法模式》说起。

 

在这一章中,作者先举了一个例子,就是Car和Engine的关系,并以此说明了为何要引入工厂方法模式,对于这个例子,只是引出问题,然后就草草收场了。接着对.NET中WebRequest对象的创建过程中的工厂模式进行了详细的剖析,最后说明了“惯例优于模式”。

 

现在说说具体的问题引入,比如我有一个Engine的类,派生出SolarEngine和GaslineEngine分别表示太阳能发动机和汽油发动机,这些发动机应用到Car这个类上面。Car这个类不用太关注,关键在与Engine类上面。

 

假设我有几个模块,其中用到了Engine下面的具体的类。比如A模块中有一个创建对象的语句

Engineengine=new SolarEngine();

B模块中也有

Engineengine=new SolarEngine();

或者C、D、E…很多模块都是这么写的。

 

现在突然有要求,把上面的所有SolarEngine类用GaslineEngine类来代替,这样的话,就不得不把所有的模块中的上面这句话替换为

Engine engine= new GaslineEngine();

也就是说,一个需求变了,结果所有的模块的代码都要改写。

 

因此,现在希望的结果就是这些模块都不改动,它们会根据其他模块(比如只需更改一个模块)就会自动地创建出GaslineEngine对象出来;更进一步地,当我有新的Engine出现,比如核发动机(NuclearEngine)吧,这些模块不用任何更改就能自动创建NuclearEngine对象。

 

然后作者说明了用工厂模式可以解决这个问题,但只是一笔带过,直接转到对.NET设计中的工厂模式的剖析去了。

 

实际上,这里提出的问题,跟后面.NET中WebRequest的例子还是不同的,为什么这么说?

在这些A,B,C….模块里,我们最希望看到的就一句话

Engineengine=Engine.Create(); //Create是Engine的静态方法

就能创建需要实际需要的对象(不管你是GaslineEngine,还是SolarEngine,还是以后的NuclearEngine)。也就是说在A,B,C…这些模块里根本看不到诸如gasline,solar这样的字眼。

 

而后面.NET的例子跟这个Engine的例子还是有点不一样的,它不是说在什么模块里面,而是当我需要创建的时候,希望用WebRequest.Create(“http://”)就创建HttpWebRequest对象;用WebRequest.Create(“file://”)就创建FileWebRequest对象。如果要套到上面的例子里面,那应该是Engine.Create(“solar”),Engine.Create(“gasline”)…;也就是说,在这些模块里面还是出现了solar这样的信息性字眼。那如果要改成其它的,岂不是所有模块都要改成Engine.Create(“gasline”)?还是不完美吧。

 

下面通过逐渐完善这个例子来说一下我的理解吧(代码尽量简单点,如果看后面那个.NET例子,有很多代码干扰较大)。分为以下三个部分:

1.实现Engine.Create(“solar”)创建SolarEngine实例

2.通过Engine.Create(),不需要更改已经写好的模块自动创建所需对象

3.“惯例优于模式”

 

先来看代码:

namespace ConsoleApplication1{    class Class1    {        [STAThread]        static void Main(string[] args)        {            Engine engine = Engine.Create("solar");            engine.StartEngine();            Engine engine2 = Engine.Create("gasline");            engine2.StartEngine();        }    }    public class Engine    {        public virtual void StartEngine(){}        public static Engine Create(string name)        {            Engine ret = null;            //这一部分明显的很业余            if (name == "solar")                ret = new SolarEngine();            else if (name == "gasline")                ret = new GaslineEngine();            return ret;        }    }    public class SolarEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Solar Engine Start...");        }           }    public class GaslineEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Gasline Engine Start...");        }    }  }


 

在这个例子中,实现了我们所说的Engine.Create(“solar”);创建SolarEngine对象,Engine.Create("gasline");创建GaslineEngine对象,而且没有用到工厂模式,如果要加入新的NuclearEngine,只需要从Engine派生一个NuclearEngine类,然后在Engine的静态方法Create里面再加一个”nuclear”判断就可以了。

 

上面这个就是简单工厂模式,但是这样做,对于一个追求完美的设计者来说,明显地很业余。因为你既然提供了Engine基类,可能就是开放的,如果别人派生出一个其它的新类,比如WaterEngine,你根本就不知道,而且别人还需要知道Create的内部实现,并且去更改这部分代码,岂不是很业余?

 

所以,这个地方我们放弃使用if…else或者switch这样的判断语句,引入一个ArrayList,保存”solar”,”gasline”,这样的关键词,并且当有人派生出NuclearEngine,WaterEngine这样的发动机时,可以修改这个ArrayList,加入”nuclear”,”engine”这样的关键词。

问题是,然后呢?怎么根据这些关键词创建具体的对象呢?

 

方法一:采用反射技术

namespace ConsoleApplication1{    class Class1    {        [STAThread]        static void Main(string[] args)        {//注册Engine,如果需要添加新的Engine,只需要//Engine.RegisterNewEngine(“nuclear”,typeof(NuclearEngine))            Engine.RegisterNewEngine("gasline", typeof(GaslineEngine));            Engine.RegisterNewEngine("solar", typeof(SolarEngine));            Engine engine = Engine.Create("solar");            engine.StartEngine();            Engine engine2 = Engine.Create("gasline");            engine2.StartEngine();        }    }//建立name与类型之间的隐射关系    public class EngineMaps    {        public string enginename = string.Empty;        public Type enginetype = null;        public EngineMaps(string _enginename, Type _enginetype)        {            enginename = _enginename;            enginetype = _enginetype;        }    }    public class Engine{//采用静态ArrayList来代替前面的if..else结构        public static ArrayList enginelist = new ArrayList();//如果需要添加新的Engine,只需要注册即可        public static void RegisterNewEngine(string _name, Type _type)        {            enginelist.Add(new EngineMaps(_name, _type));        }        public virtual void StartEngine() { }        public static Engine Create(string name)        {            Engine ret = null;            Type t = null;            foreach (EngineMaps map in enginelist)            {                if (string.Compare(map.enginename, name) == 0)                {                    t = map.enginetype;                    break;                }            }            if (t != null)            {                ret =(Engine)Activator.CreateInstance(t);            }            return ret;        }    }    public class SolarEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Solar Engine Start...");        }    }    public class GaslineEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Gasline Engine Start...");        }    }}


方法二:在没有反射技术的时候,采用工厂模式

这个也是在这本书的第一版剖析.NET的例子的时候应用的方法。

 

首先为每一个需要创建的Engine设置工厂类(GaslineEngineFactory,SolarEngineFactory),用于创建具体的实例,并且设置一个共同的接口IEngineFacory,与基类Engine对应。

namespace ConsoleApplication1{    class Class1    {        [STAThread]        static void Main(string[] args)        {            Engine.RegisterNewEngine("gasline",new GaslineEngineFactory());            Engine.RegisterNewEngine("solar", new SolarEngineFactory());            Engine engine = Engine.Create("solar");            engine.StartEngine();            Engine engine2 = Engine.Create("gasline");            engine2.StartEngine();                    }    }    public class EngineMaps    {        //建立name与creator的隐射关系,这个地方用IEngineFactory统一了工厂类,如果不用工厂,这个地方传Engine那就无法创建具体的对象了,工厂模式主要就是在这个地方把具体的对象创建工作抽象为一个IEngineFactory了,掩盖了具体的类的名称。                public string enginename = string.Empty;        public IEngineFactory creator;        public EngineMaps(string _enginename, IEngineFactory _enginecreator)        {            enginename = _enginename;            creator = _enginecreator;        }        public IEngineFactory Creator        {            get            {                return this.creator;            }            set            {                creator = value;            }        }    }    public class Engine    {        public static ArrayList enginelist = new ArrayList();                public static void RegisterNewEngine(string _name, IEngineFactory _creator)        {            enginelist.Add(new EngineMaps(_name, _creator));        }        public virtual void StartEngine() { }                public static Engine Create(string name)        {            Engine ret = null;            foreach (EngineMaps map in enginelist)            {                if (string.Compare(map.enginename, name) == 0)                {                    ret = map.Creator.MakeEngine();                    break;                }            }            return ret;        }            }    public class SolarEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Solar Engine Start...");        }    }    public class GaslineEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Gasline Engine Start...");        }    }       public interface IEngineFactory    {        Engine MakeEngine();    }    //太阳能发动机工厂创建太阳能发动机的实例    public class SolarEngineFactory : IEngineFactory    {        internal SolarEngineFactory() { }        public Engine MakeEngine()        {            Console.WriteLine("Create Solar Engine...");                        return new SolarEngine();        }    }    public class GaslineEngineFactory : IEngineFactory    {        internal GaslineEngineFactory() { }        public Engine MakeEngine()        {            Console.WriteLine("Create Gasline Engine...");            return new GaslineEngine();        }    }}


 

在采用工厂模式的情况下,如果需要扩展其他的Engine,需要

1)-------派生自Engine

public class NuclearEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Nuclear Engine Start...");        }    }

 

2)---------添加一个对应的EngineFactory,并实现IEngineFactory接口

public class NuclearEngineFactory : IEngineFactory    {        internal NuclearEngineFactory() { }        public Engine MakeEngine()        {            return new NuclearEngine();        }    }

 

3)--------通过Engine.RegisterNewEngine进行注册

Engine.RegisterNewEngine("nuclear", new NuclearEngineFactory());

4)-----具体调用过程

Engine engine3 = Engine.Create("nuclear");engine3.StartEngine();


好了,如果前面的都看懂了,那么第2部分“通过Engine.Create(),不需要更改已经写好的模块自动创建所需对象”就容易实现多了,主要就是将需要创建的具体的对象的信息保存在一个static变量里面就可以了。

staticstring Defaultenginename=”solar”;

这样A,B,C…这些模块不用更改,只需要更改

Engine.Defaultenginename=”gasline”;

或者static Type Defaultenginetype;

Engine.Defaultenginetype=typeof(GaslineEngine);

或者 static IEngineFactory DefaultFactory;

Engine.DefaultFactory= new GaslineEngineFactory();

然后定义一个不带参数的Create(),里面根据前面的静态设置实现就行了。

 

采用上述方法,就能够实现以后的A,B,C…模块不用做任何改动。如果需要更改具体类,只需要采用静态字段设置(调用的方式,而不是去更改已有模块的内部实现);或者如果需要加入新的Engine,也只是需要定义新类,调用RegisterNewEngine注册就可以了。

 

 

Ok,下面来说第三部分——“惯例优于模式”

这个主要是在使用反射进行创建的时候使用,我们在前面的“方法一”的例子当中,定义了一个EngineMaps来表示名称和类的Type之间的隐射关系,而获得Type可以用一个字符串(类名)来得到。这样只要我们在设计时统一规范,那么就能够省掉这个EngineMaps类。比如说表示一个新的Engine类,其必须在System.EngineCenter这个命名空间下面,且类名为

发动机的动力源+”Engine”,创建的时候,Create的参数名为 发动机的动力源(注意大小写)

 

下面来看具体的代码

using System;using System.Collections;using System.Reflection;namespace ConsoleApplication1{    class Class1    {        [STAThread]        static void Main(string[] args)        {//注意大小写,要让参数能转化为//System.EngineCenter.SolarEngine           System.EngineCenter.Engine engine = System.EngineCenter.Engine.Create("Solar");            engine.StartEngine();            System.EngineCenter.Engine engine2 = System.EngineCenter.Engine.Create("Gasline");            engine2.StartEngine();        }    }}//定义一个新的命名空间namespace System.EngineCenter{    public class Engine    {        public virtual void StartEngine() { }        public static Engine Create(string name)        {            Engine ret = null;            Type t = null;            //注意这个参数            t = Type.GetType("System.EngineCenter." + name+"Engine");            if (t != null)            {                ret = (Engine)Activator.CreateInstance(t);            }            return ret;        } }    public class SolarEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Solar Engine Start...");        }    }    public class GaslineEngine : Engine    {        public override void StartEngine()        {            Console.WriteLine("Gasline Engine Start...");        }    }}

既然是字符串决定的,那么就可以把这个字符串定义到配置文件当中,这样代码不需要任何更改和重新编译,只需要更改配置文件,就可以根据需要创建所需要的对象了。

原创粉丝点击