最近在看《软件设计精要与模式(第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...");
}
}
}
既然是字符串决定的,那么就可以把这个字符串定义到配置文件当中,这样代码不需要任何更改和重新编译,只需要更改配置文件,就可以根据需要创建所需要的对象了。