前言
部分摘自:《设计模式之禅》 这本书
工厂模式的定义:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使-一个 类的实例化延迟到其子类。
一个好理解的例子:
东汉《风俗通》记录了一则神话故事, 开天辟地,未有人民,女娲搏黄土做人",讲述的内容就是大家非常熟悉的女娲造人的故事。开天辟地之初,大地上并没有生物,只有苍茫大地,纯粹而洁净的自然环境,寂静而又寂寞, 于是女娟决定创造一个新物种(即人类)来增加世界的繁荣,怎么制造呢? 别忘了 女娲是神仙,没有办不到的事情,造人的过程是这样的,首先,女娲采集黄土捏成人的形状,然后放到八卦炉中烧制,最后放置到大地上生长,工艺过程是没有错的,但是意外随时都会发生。 第一次烤泥人,感觉应该熟了,往大地上一放,哇没烤熟!于是一个白人诞生了! 第二次烤泥人,上一次没烤熟,这次多烤一会儿, 放到世间一看,嘿,熟过头了,于是黑人诞生了! 第三次烤泥人,边烧制一边察看,直到表皮微责,嘿,刚好,于是黄色人种出现了!
这个造人过程是比较有意思的,是不是可以通过代码来实现这个过程呢? 在面向对象的思维中,万物皆对象,是对象我们就可以通过软件设计来实现。
- 首先对造人过程进行分析,该过程步及三个对象:女娲、八卦炉、三种不同肤色的人。
- 女娲可以使用场景类Client来表示。
- 八卦炉类似于一个工厂,负责制造产品(即人类)。
- 三种不同肤色的人,他们都是同一个抽象类(人类)下的不同实现,都是人类,只是肤色、语言不同。对于八卦炉来说都是它生产出的产品。
先定义工厂接口:
csharp
public interface IFactory
{
public T CreateHuman<T>() where T : Human;
}
定义工厂的实现类:
csharp
public class HumanFactory: IFactory
{
public T CreateHuman<T>() where T : Human
{
// 动态实例化泛型类
return Activator.CreateInstance<T>();
}
}
定义人的抽象类:
csharp
public abstract class Human
{
public abstract void GetColor();
}
定义不同肤色的人类:
csharp
public class YellowHuman : Human
{
public override void GetColor()
{
Console.WriteLine("我是黄种人!");
}
}
public class WhiteHuman : Human
{
public override void GetColor()
{
Console.WriteLine("我是白种人!");
}
}
public class BlackHuman : Human
{
public override void GetColor()
{
Console.WriteLine("我是黑人!");
}
}
客户端:
csharp
class Program
{
static void Main(string[] args)
{
// 实例化工厂类
HumanFactory factory = new HumanFactory();
Human yellowHuman = factory.CreateHuman<YellowHuman>();
yellowHuman.GetColor();
Human whiteHuman = factory.CreateHuman<WhiteHuman>();
whiteHuman.GetColor();
Human blackHuman = factory.CreateHuman<BlackHuman>();
whiteHuman.GetColor();
}
}
执行结果:
工厂模式的优点呢?我们为什么要使用工厂模式?
首先,良好的封装性代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象。只要知道这个产品的类名即可,不用知道创建对象的艰辛过程,降低模块间的耦合。
其次,工厂模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类。就可以完成 拥抱变化。例如在我们的例子中,需要增加一个棕色人种,则只需要增加一个BrownHuman类 ,工厂类不需要任何修改就可完成系统扩展。
再次,屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。
在数据库开发中,大家应该能够深刻体会到工厂方法模式的好处:如果使用JDBC连接数据库,数据库从MySQL切换到Oracle,需要改动的地方就是切换一下驱动名称(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接案例。
最后,工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象 类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不要去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类,没问题!
工厂模式的适用场景
首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类。 例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择: POP3、 IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail, 然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)。再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展。
简单工厂模式
我们这样考虑一个问题: 一个模块仅需要一个工厂类,没有必要把它生产出来,使用静态的方法就可以了,根据这一要求, 我们把上例中的HumanFactory修改一下,把生产Human的方法改成静态的,并且去掉IFactory接口就行了。 工厂类:
csharp
public class HumanFactory
{
public static T CreateHuman<T>() where T : Human
{
return Activator.CreateInstance<T>();
}
}
客户端:
csharp
class Program
{
static void Main(string[] args)
{
//HumanFactory factory = new HumanFactory();
Human yellowHuman = HumanFactory.CreateHuman<YellowHuman>();
yellowHuman.GetColor();
Human whiteHuman = HumanFactory.CreateHuman<WhiteHuman>();
whiteHuman.GetColor();
Human blackHuman = HumanFactory.CreateHuman<BlackHuman>();
blackHuman.GetColor();
}
}