举个例子,数据库访问的实现。在开发阶段,数据库采用的是Sql Server,并且完成了所有的代码开发。如果在开发结束后,由于应用场景和环境变化或客户的需求变更,需要支持Orcale,这时我们就必须对原告用Sql Server实现的部分变更为Orcale的实现。
很显然这样变化所带来的代价和成本是无法接受。因为在商业逻辑层的所有类都是调用Sql Server的实现类来完成所有数据库的操作。首先从工作量讲就非常多,不是简单替换那么简单。若站在企业级开发层面来讲,还要考虑到不同数据库之间的区别和优化,肯定还会有很多隐患。
这个案例的根本问题在商业逻辑层中的类直接对new了Sql Server的实现类,导致商业逻辑层与数据访问层的耦合度非常高。因为直接依赖了实现。因此,为了降低耦合,需要让商业逻辑层依赖于比实现更稳定的接口或抽象类。虽然降低了商业逻辑层和数据访问层的依赖关系,可最终还是在运行时依赖具体的实现类。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。这就是Factory Method的意图(Intent)。
Factory Method就是为了解决new的Lazy Load问题,其实Lazy Load就是将编译时依赖变为运行时依赖。运行时依赖就可以通过配置来设置,让其决定具体依赖于指定的实现类。
若需实现Factory Method,需要解决两个问题:
l 拥有稳定的接口或抽象类
虽然我们一直强调降低耦合度,并不是说明没有任何的依赖性。如果能理解的话,就不然理解为什么一定要有稳定的接口或抽象类的话,通常接口或抽象类的稳定性远远高于实现类。比如:一个数据库操作通常有增删改查四个操作,如果将这四个操作设计为接口,而为这四个操作的实现设计到实现这个接口的实现类。至于这个实现类就可以采用各种方法的实现,比如:Sql Server或Orcale的实现类。
l 创建实现类的子类
无论耦合度有多低,最终还是要依赖于实现类。至于创建何种实现类,就需要有一个创建实现类的子类。
l 单个对象创建
Factory Method只解决单个对象创建的问题。若是一组对象、系列对象或对象内部部分变化都是不适用的。
案例说明:
由于客户的应用环境中的数据库有Access和Sql Server。为了解决这个问题,我在创建数据库实现类的地方采用了Factory Method。同时,允许客户通过Config文件来选择支持的数据库类型。代码如下:
/// <summary>
/// 稳定的接口
/// </summary>
public interface IAutoDataAccessObject
{
/// <summary>
/// 创建一个Auto的方法
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
bool Create(string name);
}
/// <summary>
/// SqlServer的实现类
/// </summary>
public class AutoSqlServerDataAccessObject : IAutoDataAccessObject
{
/// <summary>
/// 针对SqlServer特性的具体实现方法
/// </summary>
/// <param name="name">名称</param>
/// <returns>数据库操作结果。成功返回true。反之返回false</returns>
public bool Create(string name)
{
//具体实现代码
return true;
}
}
/// <summary>
/// Access的实现类
/// </summary>
public class AutoAccessDataAccessObject : IAutoDataAccessObject
{
/// <summary>
/// 针对Access特性的具体实现方法
/// </summary>
/// <param name="name">名称</param>
/// <returns>数据库操作结果。成功返回true。反之返回false</returns>
public bool Create(string name)
{
//具体实现代码
return true;
}
}
/// <summary>
/// 工厂的抽象类
/// </summary>
public abstract class AutoDataAccessObjectFactory
{
public abstract IAutoDataAccessObject Create();
}
/// <summary>
/// SqlServer的实现类工厂具体类
/// </summary>
public class AutoSqlServerDataAccessObjectFactory : AutoDataAccessObjectFactory
{
public override IAutoDataAccessObject Create()
{
return new AutoSqlServerDataAccessObject();
}
}
/// <summary>
/// Access的实现类工厂具体类
/// </summary>
public class AutoAccessDataAccessObjectFactory : AutoDataAccessObjectFactory
{
public override IAutoDataAccessObject Create()
{
return new AutoAccessDataAccessObject();
}
}
/// <summary>
/// 根据配置文件选择采用哪种数据库访问类工厂创建类
/// </summary>
public static class DataAccessLayerHelper
{
//获取配置文件的数据库配置信息
private static readonly string databaseProvider = ConfigurationManager.AppSettings["DataProvider"].ToString();
private static AutoDataAccessObjectFactory autoDataAccessObjectFactory;
public static IAutoDataAccessObject Create()
{
if (databaseProvider.ToLower()=="sqlserver")
{
autoDataAccessObjectFactory = new AutoSqlServerDataAccessObjectFactory();
return autoDataAccessObjectFactory.Create();
}
else
{
autoDataAccessObjectFactory = new AutoAccessDataAccessObjectFactory();
return autoDataAccessObjectFactory.Create();
}
}
}
调用事例:
IAutoDataAccessObject autoDataAccessObject = DataAccessLayerHelper.Create();
数据访问实现类跟使不使用Factory Method没有直接关系,即使你不使用Factory Method,也要使用。但是,工厂抽象类和支持各个数据库实现类的工厂类会变得非常多,显然这种“类爆炸”是无法接受的,因此需要对此进行优化。首先想到的优化方法就是通过反射来实现,通过传入的数据对象类型和配置文件中的数据库类型的信息拼接成一个字符串,然后将这个字符串(其实就是完整的数据库访问实现类的类名),最后通过Activator.CreateInstance方法获得其类的实例。当然前提还需定义一个更抽象的IDAtaAccessObject接口,让各个数据访问接口继承这个接口。当然这样的实现有类型不安全性和性能的问题,需要进一步处理。
就个人经验和感觉而言,Factory Method一般不会单独使用。因为,Factory Method只是解决了某一个模块或架构层对具体实现类的依赖性(如:商业逻辑层不依赖于具体数据库访问实现类),还是没有最终完全解决依赖问题(如:DataAccessHelper类还是要依赖于具体工厂实现类),总会在一个地方会依赖于某一个具体的实现类,至少是具体工厂实现类(任何模式都不可能解决所有的问题,这也是很正常的,不能否定Factory Method给我们设计带来的好处),所以Factory Method都会和其它模式、反射和配置文件共同配合完成降低耦合度。
浙公网安备 33010602011771号