提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
在文章一开始,作者提出针对相同的程序采用不同的数据库,从而采用工厂模式。
以下结构图为对User表和Department表采用的sqlserver和access的不同的具体实现,然后提取到各自的Factory类中去。
using System;
usingSystem.Collections.Generic;
using System.Text;
namespace 抽象工厂模式
{
class Program
{
static void Main(string[] args)
{
User user = new User();
Department dept = new Department();
//AbstractFactoryfactory = 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();
}
}
//某个产品
class User
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
//另一个产品
class Department
{
private int _id;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _deptName;
public string DeptName
{
get { return _deptName; }
set { _deptName = value; }
}
}
interface IUser
{
void Insert(User user);
User GetUser(int id);
}
//以下为该产品的不同两种实现
class SqlserverUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Sqlserver中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到User表一条记录");
return null;
}
}
class AccessUser : IUser
{
public void Insert(User user)
{
Console.WriteLine("在Access中给User表增加一条记录");
}
public User GetUser(int id)
{
Console.WriteLine("在Access中根据ID得到User表一条记录");
return null;
}
}
interface IDepartment
{
void Insert(Department department);
Department GetDepartment(int id);
}
//以下为该产品的不同两种实现
class SqlserverDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Sqlserver中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Sqlserver中根据ID得到Department表一条记录");
return null;
}
}
class AccessDepartment : IDepartment
{
public void Insert(Department department)
{
Console.WriteLine("在Access中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
Console.WriteLine("在Access中根据ID得到Department表一条记录");
return null;
}
}
//工厂方法
interface IFactory
{
IUser CreateUser();
IDepartment CreateDepartment();
}
//为每种实现提供一个具体工厂
class SqlServerFactory : IFactory
{
public IUser CreateUser()
{
return new SqlserverUser();
}
public IDepartment CreateDepartment()
{
return new SqlserverDepartment();
}
}
class AccessFactory : IFactory
{
public IUser CreateUser()
{
return new AccessUser();
}
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}
}
以下为抽象工厂模式的具体结构图:
AbstractA和AbstractB是两个抽象的产品,之所以为抽象,因为他们都可能有两种不同的实现
ProductA1、ProductA2、ProductB1、ProductB2就是对两个抽象产品的具体分类的实现.
通常我们是在运行时创建一个ConcretFactory类的实例,这个具体的工厂再继续创建具有特定实现的产品对象,也就是说,为了创建不同的产品对象,客户端应该使用不同的具体工厂
抽象工厂模式的优点与缺点
最大的好处在于交换产品系列,由于具体工厂类,例如 IFactoryfactory = new AccessFactory();在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置
其次,他让具体的创建实例的过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端具体代码中。
但是,缺点也很明显,如果我们需要增加一个产品,那么要增加的就多的多了,比如AbstractProductC,ProductC1, ProductC2, 还要在Factory类中增加生产产品的函数,这样一来,明显十分麻烦。
既然如此麻烦,我们不如将工厂类去掉,转而使用简单工厂模式:
class DataAccess
{
private static readonly string db = "Sqlserver";
//private staticreadonly string db = "Access";
public static IUser CreateUser()
{
IUser result = null;
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;
}
}
虽然不用再增加product的时候添加无数类了,但正如我们之前所学过的,我们却需要修改程序代码,违背了开闭原则。
此时我们需要一个技术,如果是sqlserver就是去实例化Sql server数据库相关类,如果是Access就去实例化Access相关类,而且在增加product(比如增加Oracle数据库)时不需要修改原本代码内容本身那该多好!
这就是用到反射,格式:
Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”)
只需要在顶端写上 usingSystem.Reflection;就可以
举个例子:
常规情况下,我们写成这样:
IUser result = new SqlserverUser();
可我们用了反射,就是这样:
Using System.Reflection;
IUser result = (IUser)Assembly.Load(“抽象工厂模式”).CreateInstance(“抽象工厂模式.SqlserverUser”);
我们可以发现,上下两种方法看上去一样,但是反射方法将类名变成了字符串,可以根据需要来替换字符串变量,而不再使用switch或if,那样的话在增加product的时候虽然必要的功能类肯定是要增加的,但代码就不需要修改了
代码如下:
class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = "Sqlserver";
//private staticreadonly string db = "Access";
public static IUser CreateUser()
{
string className =AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className =AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
虽然简化了许多,每次只需要修改db字符串,但能不能不修改代码里的字符串呢?
下面我们就用到配置文件与反射相结合了
添加如下的配置文件:
<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>
<appSettings>
<add key=”DB” value=”Sqlserver”>
</appSettings>
</configuration>
并添加引用 System.configuratio15.,在程序头添加
Using System.Configuration;
然后更改DataAccess类的字段DB的赋值码
string db = ConfigurationManager.AppSettings[“DB”];
这下就解决了修改程序中db字符串的问题了
class DataAccess
{
private static readonly string AssemblyName = "抽象工厂模式";
private static readonly string db = ConfigurationManager.AppSettings["DB"];
public static IUser CreateUser()
{
string className =AssemblyName + "." + db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment()
{
string className =AssemblyName + "." + db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
总的来说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合