上一篇文章我们实现了简单工厂模式,简单工厂模式虽然简单,但存在一个很严重的问题:当系统中需要引入新产品时,由于静态工厂方法通过传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背开闭原则。如何实现增加新产品而不影响已有代码?工厂方法模式为此应运而生。本篇将介绍一下第2种工厂模式---工厂方法模式。
在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。
首先我们看一结构图:
从图中可以看出,在工厂方法模式结构图中包含4个角色。
1、Product (抽象产品)
2、ConcreteProduct(具体产品)
3、Factory(抽象工厂)
4、ConcreteFactory(具体工厂)
代码实现:
定义抽象工厂:
//抽象工厂
public interface ILoggerFactory
{
public ILogger CreateLogger();
}
实现file logger:
public class FileLoggerFactory : ILoggerFactory
{
//实现FileLogger
public ILogger CreateLogger()
{
ILogger logger = new FileLogger();
return logger;
}
}
实现database logger:
public class DatabaseLoggerFactory : ILoggerFactory
{
//实现DatabaseLogger
public ILogger CreateLogger()
{
ILogger logger = new DatabaseLogger();
return logger;
}
}
定义抽象产品:
public interface ILogger
{
public void WriteLog();
}
实现具体产品的file log:
public class FileLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("file log");
}
}
实现具体产品的db log:
public class DatabaseLogger : ILogger
{
public void WriteLog()
{
Console.WriteLine("db log");
}
}
最后我们用console app 来打印一下结果:
class Program
{
static void Main(string[] args)
{
//选择使用哪个工厂
ILoggerFactory loggerFactory = new FileLoggerFactory();
//工厂不同产品也就不同,这里不关心产品的具体实现
ILogger logger = loggerFactory.CreateLogger();
//输出产品
logger.WriteLog();
}
}
结果如下:
大多数时候我们习惯用config去决定代码的功能,这样才够灵活。
所以我们配置一下config:
<?xml version="1.0" encoding="utf-8" ?>
<config>
<chartType>DatabaseLoggerFactory</chartType>
</config>
写一个静态方法读取xml config:
public static string GetConfig()
{
XmlDocument doc = new XmlDocument();
doc.Load(@"Config.xml");
XmlNode root = doc.SelectSingleNode("config");
return root.FirstChild.InnerText;
}
我们拿到的sring,接下来要用这个string 来确定去实例化哪个类:(这里用到了反射)
/// <summary>
/// 创建对象实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="fullName">命名空间.类型名</param>
/// <param name="assemblyName">程序集</param>
/// <returns></returns>
public static T CreateInstance<T>(string fullName, string assemblyName)
{
string path = fullName + "," + assemblyName;//命名空间.类型名,程序集
Type o = Type.GetType(path);//加载类型
object obj = Activator.CreateInstance(o, true);//根据类型创建实例
return (T)obj;//类型转换并返回
}
然后在app 里就可以写成这样了:
class Program
{
static void Main(string[] args)
{
//通过反射来拿到实例
ILoggerFactory loggerFactory = CreateInstance<ILoggerFactory>
("PolymorphicFactoryPattern." + GetConfig(), "PolymorphicFactoryPattern");
ILogger logger = loggerFactory.CreateLogger();
logger.WriteLog();
}
}
结果如下:我们拿到了db log 的输出,说明code work。
这里调用抽象工厂的代码可以隐藏起来,封装到抽象工厂里面:
public interface ILoggerFactory
{
public ILogger CreateLogger();
//这样抽象工厂调用抽象产品的代码就被隐藏了
public void WriteLog()
{
ILogger logger = this.CreateLogger();
logger.WriteLog();
}
}
而app 运行代码也变得更简单了:
static void Main(string[] args)
{
//接口
ILoggerFactory loggerFactory = CreateInstance<ILoggerFactory>("PolymorphicFactoryPattern." + GetConfig(), "PolymorphicFactoryPattern");
loggerFactory.WriteLog();
}
总结:
工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。
优点:
1、工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
2、它能让工厂可以自主确定创建何种产品对象,而如果创建这个对角的细节完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正因为所有具体工厂都具有同一抽象父类。
3、在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其它具体产品和具体工厂。而只要添加一个具体工厂和具体厂品即可。这样符合开闭原则。
缺点:
1、扩展时需要成对增加具体工厂和具体产品,类比较多增加系统的额外开销。
2、系统要用到多态,反射、dom 等技术增加了实现难度。
适用场景:
1、客户端不知道其所需要的对象的类。客户端不需要知道具体产品类的类名,只需要知道对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储配置文件或db中。
2、抽象工厂类通其子类来指定创建哪个对象。一个抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,对用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。