15. 抽象工厂模式
约 2168 字大约 7 分钟
2026-03-10
定义
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
人话:抽象工厂模式就像项目里需要把数据库从 SQL Server 切换到 Access。如果代码中到处都是具体的 SQL Server 操作语句,比如 SqlConnection、SqlCommand 等,一旦要换数据库,就得把这些地方一个个改掉,不仅麻烦还很容易出错,这在软件中就属于依赖具体实现、耦合过高。
而使用抽象工厂模式之后,程序只依赖一套统一的数据库操作接口,比如“创建用户表操作”“创建订单表操作”等。当需要从 SQL Server 切换到 Access 时,只需要把对应的工厂实现换成 Access 的工厂,程序其他地方都不用修改。这样就相当于应用程序只和“数据库工厂”打交道,而具体是 SQL Server 还是 Access,由不同的工厂去负责实现。
抽象工厂模式(Abstract Factory)结构图
举例
通过抽象工厂模式切换数据库举例
IDepartment 接口:
public interface IDepartment
{
void Insert(Department department);
Department GetDepartment(int id);
}SqlServerDepartment 类:
public class SqlServerDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在 SQLServer 中给 Department 表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在 SQLServer 中根据 ID 得到 Department 表一条记录");
return null;
}
}AccessDepartment 类:
public class AccessDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在 Access 中给 Department 表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在 Access 中根据 ID 得到 Department 表一条记录");
return null;
}
}IFactory 接口(抽象工厂):
public interface IFactory
{
IUser CreateUser();
IDepartment CreateDepartment();
}SqlServerFactory 类:
public class SqlServerFactory : IFactory
{
public IUser CreateUser()
{
return new SqlServerUser();
}
public IDepartment CreateDepartment()
{
return new SqlServerDepartment();
}
}AccessFactory 类:
public class AccessFactory : IFactory
{
public IUser CreateUser()
{
return new AccessUser();
}
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}客户端代码:
class Program
{
static void Main(string[] args)
{
User user = new User();
Department dept = new Department();
//IFactory factory = new SqlServerFactory();
IFactory factory = new AccessFactory();
IUser iu = factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = factory.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
}运行结果(使用 AccessFactory):
在Access中给 User 表增加一条记录
在Access中根据 ID 得到 User 表一条记录
在Access中给 Department 表增加一条记录
在Access中根据 ID 得到 Department 表一条记录优点:
最大的好处便是易于交换产品系列,由于具体工厂类,例如
IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。第二大好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点:
抽象工厂模式可以很方便地切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表Project,那就至少要增加三个类,IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。
还有就是我的客户端程序类显然不会是只有一个,有很多地方都在使用IUser或IDepartment,而这样的设计,其实在每一个类的开始都需要声明
IFactory factory = new SqlserverFactory(),如果我有100个调用数据库访问的类,是不是就要更改100次IFactory factory = new AccessFactory()这样的代码才行?这不能解决我要更改数据库访问时,改动一处就完全更改的要求。
简单工厂改进抽象工厂
简单工厂模式改进抽象工厂模式
DataAccess 类:
public class DataAccess
{
// 数据库名称(可切换为 Access)
private static readonly string db = "SqlServer";
//private static readonly string db = "Access";
public static IUser CreateUser()
{
IUser result = null;
// 根据 db 的设置实例化相应对象
switch (db)
{
case "SqlServer":
result = new SqlServerUser();
break;
case "Access":
result = new AccessUser();
break;
}
return result;
}
public static IDepartment CreateDepartment()
{
IDepartment result = null;
switch (db)
{
case "SqlServer":
result = new SqlServerDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
}
return result;
}
}客户端代码:
class Program
{
static void Main(string[] args)
{
User user = new User();
Department dept = new Department();
// 直接得到实际的数据库访问实例
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
}与其用那么多工厂类,不如直接用一个简单工厂来实现,抛弃了IFactory、SqlserverFactory和 AccessFactory三个工厂类,取而代之的是DataAccess类,由于事先设置了db的值(Sqlserver或Access),所以简单工厂的方法都不需要输入参数,这样在客户端就只需要
DataAccess.CreateUser()和DataAccess.CreateDepartment()来生成具体的数据库访问类实例,客户端没有出现任何一个SQLServer或Access的字样,达到了解耦的目的。这样改确实是比之前的代码要更进一步了,客户端已经不再受改动数据库访问的影响了。可是如果我需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就比较麻烦了。这样就需要在DataAccess类中每个方法的swicth中加case了。
反射+抽象工厂
采用反射技术
DataAccess 类(使用反射创建对象):
using System.Reflection;
public class DataAccess
{
// 程序集名称
private static readonly string AssemblyName = "抽象工厂模式";
// 数据库名称(可替换为 Access)
private static readonly string db = "SqlServer";
//private static readonly string db = "Access";
// 创建 User 对象
public static IUser CreateUser()
{
string className = AssemblyName + "." + db + "User";
return (IUser)Assembly
.Load(AssemblyName)
.CreateInstance(className);
}
// 创建 Department 对象
public static IDepartment CreateDepartment()
{
string className = AssemblyName + "." + db + "Department";
return (IDepartment)Assembly
.Load(AssemblyName)
.CreateInstance(className);
}
}现在如果我们增加了 Oracle数据访问,相关的类的增加是不可避免的,这点无论我们用任何办法都解决不了,不过这叫扩展,开放-封闭原则性告诉我们,对于扩展,我们开放。但对于修改,我们应该要尽量关闭,就目前而言,我们只需要更改
private static readonly string db = "Sqlserver";为private stati creadonly string db = "Oracle";就意味着(IUser)Assembly.Load(AssemblyName).Createlnstance(className);这一句发生了变化。这样的结果就是DataAccess.CreateUser()本来得到的SqlserverUser的实例,而现在变成了OracleUser 的实例了。如果我们需要增加Project产品时,只需要增加三个与Project相关的类,再修改DataAccesss,在其中增加一个
public static IProject CreateProject()方法就可以了。后续可以进一步利用配置文件来解决更改DataAccess的问题的。通过读文件来给DB字符串赋值,在配置文件中写明是Sqlserver还是Access, 这样就连DataAccess类也不用更改了。
通过应用反射+抽象工厂模式解决了数据库访问时的可维护、可扩展的问题。从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。
