工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,弥补了简单工厂模式的缺点,在增加新的具体产品对象时不需要对已有系统做任何修改
一、工厂方法模式概述
学习难度:⭐⭐ 使用频率:⭐⭐⭐⭐⭐
定义:定义一个用于创建对象的接口,但是让子类决定将哪个类实例化。工厂方法模式让一个类的实例化延迟到子类中
工厂方法模式又称工厂模式(Factory Pattern),又称虚拟构造器模式或多态工厂模式。在工厂模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
二、工厂方法模式的结构与实现
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来实现具体工厂方法,创建具体的产品对象
1:工厂方法模式的结构
1)Product(抽象产品)
它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类
2)ConcreteProduct(具体产品)
它实现了抽象产品接口,某种具体类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应
3)Factory(抽象工厂)
在抽象工厂中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口
4)ConcreteFactory(具体工厂)
它是抽象工厂类的子类,实现了在抽象工厂中声明的工厂方法,由客户端调用,返回一个具体产品类的实例
2:工厂方法模式的实现
工厂模式相比抽象工厂,其实就是引入了一个抽象工厂的角色,抽象工厂可以是接口,也可以是抽象类或具体类。典型代码如下:
interface Factory
{
Product FactoryMethod();
}
在抽象工厂中声明了工厂方法但是并未实现,具体产品对象的创建由其子类负责,客户端针对抽象工厂编程,可在运行时指定具体工厂类,具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品,典型代码如下:
class ConcreteFactory :Factory
{
public Product FactoryMethod()
{
return new ConcreteProduct
}
}
三、工厂方法模式的应用实例
在之前的博客中使用了鼠标的案例来详细完成了工厂模式的结构和代码实现
零基础自学C#——Part5:委托和设计模式_代码历险记的博客-CSDN博客
这里引用另外一个案例来实现,需求如下:
某系统运行日志记录器,可以通过多种途径保存系统的运行日志,例如通过文件记录和数据库记录,用户可以通过修改配置文件灵活地更改日志记录方式。
在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的顺序,否则会记录失败。
Logger接口充当抽象产品
namespace FactoryMethodSample
{
interface Logger //创建接口,充当抽象产品
{
void WriteLog();
}
}
其子类FileLogger和DatabaseLogger充当具体产品
namespace FactoryMethodSample
{
class FileLogger:Logger //文件日志记录器,充当具体产品对象
{
public void WriteLog()
{
Console.WriteLine("文件日志记录");
}
}
}
namespace FactoryMethodSample
{
class DatebaseLogger:Logger //数据库日志记录器,充当具体产品角色
{
public void WriteLog()
{
Console.WriteLine("数据库日志记录");
}
}
}
LoggerFactory接口充当抽象工厂
namespace FactoryMethodSample
{
interface LoggerFactory //日志记录器工厂接口,充当抽象工厂
{
Logger CreateLogger();
}
}
其子类FileLoggerFactory和DatabaseLoggerFactory充当具体工厂
namespace FactoryMethodSample
{
class FileLoggerFactory:LoggerFactory //文件日志记录器工厂类,具体工厂
{
public Logger CreateLogger()
{
//创建文件日志记录器对象
Logger logger = new FileLogger();
//初始化数据日志记录器
return logger;
}
}
}
namespace FactoryMethodSample
{
class DatebaseLoggerFactory:LoggerFactory //数据库日志记录器工厂类,具体工厂
{
public Logger CreateLogger()
{
//创建数据库日志记录器对象
Logger logger = new DatebaseLogger();
//初始化数据日志记录器
return logger;
}
}
}
客户端的实现
namespace FactoryMethodSample
{
class Program
{
static void Main(string[] args)
{
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory(); //可引入配置文件实现
logger = factory.CreateLogger();
logger.WriteLog();
Console.Read();
}
}
}
输出结果为:
文件日志记录。
如果需要更改日志记录器,只需要修改客户端代码中具体工厂类类名即可,例如:
factory = new DatebaseLoggerFactory();
输出结果为:
数据库日志记录。
如果需要增加并使用新的日志记录器,只需要对应增加一个新的具体工厂类,在客户端修改代码即可
四、配置文件与反射
其实在客户端修改代码,对于客户端而言并不符合开闭原则,在实际开发中,可以对具体工厂类的实例化过程进行改进,在客户端代码中不直接使用new关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件(例如XML文件)中,再通过程序集的反射机制,读取配置文件中存储的类名字符串生成对象
例如将FileLoggerFactory存储在XML格式的文档中:
<?xml version = "1.0" encoding = "utf - 8"?>
<configuration>
<appSettings>
<add key ="factory"value="FactoryMethodSample.FileLoggerFactory"/>
</appSettings>
</configuration>
在该文件中FactoryMethodSample为工厂类所在的命名空间,FileLoggerFactory为具体工厂类的类名,在.NET中,配置文件一般以config作为扩展名,例如App.config,Web.config等
/*
.NET反射机制的介绍
反射(Refalection)是.NET重要机制之一,通过反射可以在运行时获得.NET中每个类型(类,结构,委托,接口,枚举等)的成员,包括方法、属性、事件,以及构造函数等,还可以获取每个成员的名称、限定符参数等。
由于在.NET的程序集(Assembly)中封装了类型元数据信息,因此可以先通过Assembly的Load(程序集名称)方法加载一个程序集,再通过CreateInstance("命名空间.类")方法根据类名创建一个object类型的对象,用户可以根据需求将其转换为所需类型。代码如下
//导入命名空间
using system.Reflection;
object obj = Assembly.Load("程序集名称").CreateInstance(“命名空间.类”);
在上述代码中,“命名空间.类”可以存储在配置文件中,使用ConfugurationManager类的Appsetting属性可以获取存储在配置文件中的类名字符串
*/
引入配置文件和反射机制后,早客户端测试代码中,无需使用new关键字来创建具体的工厂类,而是将具体工厂类名放在配置文件中,再通过读取配置文件和反射机制来动态创建对象。代码如下:
using System.Configuration;
using System.Reflection;
namespace FactoryMethodSample
{
class Program
{
static void Main(string[] args)
{
LoggerFactory factory; //针对抽象工厂类编程
Logger logger;
//读取配置文件
string factoryString = ConfigurationManager.AppSettings["factory"];
//反射生成对象
factory=(LoggerFactory)Assembly.Load("FactoryMethodSample").CreateInstance(factoryString);
logger.WriteLog();
Console.Read();
}
}
}
在引入配置文件和反射机制后,如果需要增加一种新类型的日志记录方式,需要四个步骤:
1:在新的日志记录器类需要继承抽象日志器类Logger
2:增加一个新的具体日志记录器工厂,继承抽象日志记录器工厂类Logger和LoggerFactory,并且实现其中的方法CreateLogger(),返回具体的日志记录器对象
3:修改配置文件,以新增的具体日志记录器工厂类的类名字符串替换为原有的工厂类的类名字符串
4:编译新增的具体日志记录器类和具体日志记录器工厂类,运行客户端测试类即可
其实在很多设计模式种都使用了配置文件和反射机制来创建对象
五、工厂方法的重载
可以通过多种方式来初始化同一个产品类,但是对于同一个具体工厂而言,无论使用哪种工厂方法,所创建的产品类型均要相同。
引入重载方法后,抽象工厂类LoggerFactory代码修改如下:
namespace FactoryMethodSample
{
interface LoggerFactory //日志记录器工厂接口,充当抽象工厂
{
Logger CreateLogger();
Logger CreateLogger(string str);
Logger CreateLogger(object obj);
}
}
具体工厂类DatabaseLoggerFactory代码修改如下:
namespace FactoryMethodSample
{
class DatebaseLoggerFactory:LoggerFactory //数据库日志记录器工厂类,具体工厂
{
public Logger CreateLogger()
{
//创建数据库日志记录器对象
Logger logger = new DatebaseLogger();
//初始化数据日志记录器
return logger;
}
public Logger CreateLogger(string str)
{
Logger logger= new DatebaseLogger();
return logger;
}
public Logger CreateLogger(object obj)
{
Logger logger = new DatebaseLogger();
return logger;
}
}
}
六、工厂方法的隐藏
有时为了进一步简化客户端,还可以对客户端隐藏工厂方法,此时在工厂类中直接调用产品类的业务方法,在客户端无需调用工厂方法创建的产品对象,直接使用工厂对象即可
在上述例子中,抽象工厂类LoggerFactory的代码修改如下:
namespace FactoryMethodSample
{
abstract class LoggerFactory //日志记录器工厂接口,充当抽象工厂
{
//在工厂类中直接调用日志记录器类的业务方法WriteLog()
public void WriteLog()
{
Logger logger=this.CreateLogger();
logger.WriteLog();
}
}
public abstract Logger CreateLogger();
}
客户端代码修改如下:
using System.Configuration;
using System.Reflection;
namespace FactoryMethodSample
{
class Program
{
static void Main(string[] args)
{
LoggerFactory factory; //针对抽象工厂类编程
//读取配置文件
string factoryString = ConfigurationManager.AppSettings["factory"];
//反射生成对象
factory=(LoggerFactory)Assembly.Load("FactoryMethodSample").CreateInstance(factoryString);
logger.WriteLog();
Console.Read();
}
}
}