闲谈工厂方法
设计模式系列到了第四篇了,如果还不谈谈工厂方法设计模式就太对不起GoF了,为什么有如此一说?实际上工厂方法模式是好些模式的基石,她们或多或少的使用了工厂方法模式或以工厂方法为模型。
工厂方法模式是一种类创建型模式,她为创建一个对象提供了一个稳定的接口,而将对象创建的真正工作推迟到其子类实现,工厂方法允许将类的初始化工作延迟到子类,让子类决定实例化哪个具体的类。实际上很多时候对象的创建工作往往会发生变化,直接的去用new实例化对象会导致紧耦合,当需求发生变化的时候依赖项也会跟着发生变化。按照我们解决这类问题的一般原则:首先寻找变化点――对象创建,然后封装变化――用子类来封装,父类给客户程序提供稳定的接口,不稳定的地方给封装了,客户可见的只有稳定的父类接口,这也是我们面向对象开发人员一直所追求的目标。
工厂方法举例
既然是谈工厂方法模式,我们就来用工厂举例吧。在举例之前先用一个类图来大致了解一下GoF的工厂方法模式的结构。
DEMO
问题描述:可口可乐公司的可乐瓶上粘贴广告条的工序,当一个可乐瓶到来的时候控制程序调用贴广告的类提供产生一个广告条的接口,假设流水线上来的可乐瓶有大有小(现实中肯定是不可能的了),那么这个接口就应该提供这样的实现:根据不同的可乐瓶产生不同的广告纸,这个广告纸的产生就是一个需求经常变化的环节(根据公司市场促销的变化广告纸需要变化等等)。
不用模式的实现:
{
public void 矿泉水瓶到来(int 型别)
{
广告纸 ggz = Get广告纸(型别);
//贴上去
}
广告纸 Get广告纸(int型别码)
{
switch(型别码)
{
case A : return new A广告纸();break;
case B : return new B广告纸();break;
//……..
}
}
}
当然,如果需求不再变化这种实现没有什么不妥,而且性能高效。但是不存在需求不变的程序,产生广告纸需求变化我们就得不断的修改这个switch,这个流水线要经常改变,而且车间里的流水线往往不止一条,改动起来非常麻烦,意大利面条式的程序出现了(违反了开闭原则)。
为了向客户提供稳定的接口我们将产生广告纸的实现延迟到子类实现
{
public virtual 广告纸 Get广告纸(int 型别码)
{
return new 默认广告纸();
}
}
public class ConcreteCreator : Creator
{
public override 广告纸 Get广告纸(int 型别码)
{
switch(型别码)
{
case A : return new A广告纸();break;
case B : return new B广告纸();break;
//……..
//如果没有满足的型别码则调用父类的实现返回一个默认广告纸
default : base.Get广告纸(型别码);
}
}
}
其中 默认广告纸,A广告纸,B广告纸都是广告纸的子类。然后流水线就依赖这个Creator类,Creator类是一个稳定的类。有人说,把广告纸创建的工作分离到另外一个类就可以了,干吗那么麻烦还要弄个父类,然后弄个继承啊。记不记得有个原则叫SDP(稳定依赖原则,朝着稳定的方向进行依赖)?流水线依赖这个稳定的Creator类,子类让他自己弄去吧。
看到上面的例子我们并没有抑制变化的发生(这是不可能的),但是我们封装了变化而提供了不变的接口。
DotNet中的工厂方法
ADO.net 2.0中微软在原来的IDBConnection和具体实现 如 SqlConnection等之间增加了一个抽象类DBConnection,将一些重复东西放到这个抽象层。DBConnection里有个CreateCommand,这就是一个工厂方法,由于有了这个方法的存在我们可以写出这样的代码:
{
using (DbConnection conn = GetConnection())
{
User u;
using (DbCommand comm = conn.CreateCommand())
{
comm.CommandType = CommandType.Text;
comm.CommandText = GETUSERBYUSERNAME_TRAP_USER;
SetUserNameParam(userName, comm);
conn.Open();
using (DbDataReader reader = comm.ExecuteReader(CommandBehavior.CloseConnection))
{
if (!reader.Read()) return null;
u = new User();
u.UserId = reader["userid"].ToString();
u.UserName = reader["uname"].ToString();
u.UserPwd = reader["pwd"].ToString();
reader.Close();
}
comm.Parameters.Clear();
}
conn.Close();
return u;
}
}
你看得出来这是针对那种数据的实现么?看不出吧,实现了数据库无关性,将变与不变分开了,客户程序面对着稳定的接口。
工厂方法在DotNet中的演进
第一:我们可以利用反射机制化解switch语句的泥潭,在上面可口可乐的例子中我们可以给工厂方法传入一个类的限定名利用反射产生实例,去掉switch语句。这样还可以去掉子类了
新的实现
………………
public 广告纸 Get广告纸(string className)
{
Assembly asm = Assembly.LoadFrom(className+”.dll”);
广告纸 o = asm.CreateInstance (className) as 广告纸;
return o;
}
上面所说的两种演进虽然已经失去了工厂方法模式原来的型构,但依然得到我们想要的:封装变化,为客户程序提供稳定的接口,不将需求的变化扩散到客户程序。所以说模式是灵活的,模式要这么多模式要表达的是她们的思想并不是模式的实现形式。
工厂方法和其他模式的关系
与抽象工厂模式:抽象工厂模式实际上是包含了很多工厂方法的特例,抽象工厂模式就是将这些工厂方法阻止起来,利用这些工厂方法创建一系列相关的对象。
模板方法:上一篇中我们描述了模板方法模式,实现模板方法算法中的每一小步就是工厂方法了
总结
在这一篇中我们引出了软件开发的真谛:寻找变化,封装变化。其实我们学习OO,学习设计模式要达到的就是这个目的。将变与不变相分离,将以后的维护点统一、集中起来,不让变化分散在各处。在敏捷开发中有一个名言:拥抱变化。是的,我们不能避免软件开发中的变,如果没有变,我想软件开发也失去了她灿烂的色彩。但是我们可以隔离变化,勇敢的面对这些变化才会演绎出软件之美,才造就了GoF、Martin Folwer这些大家(呵呵,最重要的是有了变化我们这些程序员的饭碗才没有完全被代码生成器所代替)。