抽象工厂的目的是提供一个接口,使得使用者在不必指定产品的具体类型情况下,创建多个
产品族中的产品对象。先看结构图:
再看示例代码:
现在有两个类型的产品族,他们的抽象基类分别是ProductA,ProductB。
现在我们要改造这段代码使它与sqlclient的解耦,那就必须找他们的抽象基类了。当然微软已经为我们准备好了,在System.Data.Common命令空间有DbConnection、DbCommand、DbParameter、DbDataReader等等一系列相关的抽象类。我们可以把这些类看成一个产品族,显然它们都是相关联的,数据库连接对象DbConnection要执行命令,就得有sql命令对象DbCommand,命令对象又要使用参数对象DbParameter。
而抽象工厂 DbProviderFactory类定义了生产这些产品方法CreateConnection、CreateCommand......。具体工厂实现由各数据提供程序提供,譬如SqlClient的是SqlClientFactory。再看非完整的结构图:
这是一个标准的抽象工厂模式实现。以下是改造后代码:
再看示例代码:
现在有两个类型的产品族,他们的抽象基类分别是ProductA,ProductB。
//抽象工厂接口
public interface ICreator
{
ProductA factoryA();//创建A族产品
ProductB factoryB();//创建B族产品
}
//具体工厂类1
public class ConcreteCreator1 implements ICreator
{
@Override
public ProductA factoryA()
{
return new ConcreteProductA1();
}
@Override
public ProductB factoryB()
{
return new ConcreteProductB1();
}
}
//具体工厂类2
public class ConcreteCreator2 implements ICreator
{
@Override
public ProductA factoryA()
{
return new ConcreteProductA2();
}
@Override
public ProductB factoryB()
{
return new ConcreteProductB2();
}
}
//调用类
public class Client
{
public static void main(String[] args)
{
ICreator creator1=new ConcreteCreator1();
ICreator creator2=new ConcreteCreator2();
ProductA proa1=creator1.factoryA();
ProductB prob1=creator1.factoryB();
ProductA proa2=creator2.factoryA();
ProductB prob2=creator2.factoryB();
//....
}
}
这里我要提出的问题是具体工厂类的实现中,每个工厂方法返回类型都是抽象类,而具体要返回哪个实现类并未规定,可以返回任何子类。 这里返回的实际子类对调用者是透明的。如ConcreteCreator1的factoryA方法可以返回ConcretProductA2,客户端本来想要调用ConcreteCreator1得到ConcretProductA1,结果可能是ConcretProductA2。
问题出现的原因是一个具体工厂类中所生产的不同类型的产品之间没有关联,以至程序员们可以随意搭配,这样抽象工厂已经毫无意义了。而抽象工厂的意义正是在于它要生产的是一组有关联的产品族,从代码的角度来说工厂实现类各工厂方法所生产的实际产品必须是有关联的。下面我们以C#的ado.net抽象工厂DbProviderFactory为例说明,先看一段普通的数据库查询代码:
string connStr = "Persist Security Info=False;Initial Catalog=mydb;server=(local);User ID=sa;Pwd=xxx";
string sql = "select * from userinfo where id=@id";
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql,conn);
SqlParameter param = new SqlParameter();
param.ParameterName = "id";
param.DbType = DbType.Int32;
param.Value = 4;
cmd.Parameters.Add(param);
SqlDataReader dataReader = cmd.ExecuteReader();
if (dataReader.Read())
{
Console.WriteLine(dataReader.GetString(0));
}
}
这段使用System.Data.SqlClient查询数据的代码很常见,但按依赖倒转原则来说它与System.Data.SqlClient耦合太深了。.net中数据提供程序还有System.Data.Odbc、System.Data.OleDb、 System.Data.OracleClient等,如果换了其他数据提供程序那我们整个代码都得被翻新一遍了。
现在我们要改造这段代码使它与sqlclient的解耦,那就必须找他们的抽象基类了。当然微软已经为我们准备好了,在System.Data.Common命令空间有DbConnection、DbCommand、DbParameter、DbDataReader等等一系列相关的抽象类。我们可以把这些类看成一个产品族,显然它们都是相关联的,数据库连接对象DbConnection要执行命令,就得有sql命令对象DbCommand,命令对象又要使用参数对象DbParameter。
而抽象工厂 DbProviderFactory类定义了生产这些产品方法CreateConnection、CreateCommand......。具体工厂实现由各数据提供程序提供,譬如SqlClient的是SqlClientFactory。再看非完整的结构图:
这是一个标准的抽象工厂模式实现。以下是改造后代码:
DbProviderFactory DbProvider = SqlClientFactory.Instance;
using (DbConnection conn = DbProvider.CreateConnection())
{
conn.Open();
DbCommand cmd = DbProvider.CreateCommand();
cmd.Connection = conn;
cmd.CommandText = sql;
DbParameter param = DbProvider.CreateParameter();
param.ParameterName = "id";
param.DbType = DbType.Int32;
param.Value = 4;
cmd.Parameters.Add(param);
DbDataReader dataReader = cmd.ExecuteReader();
if (dataReader.Read())
{
Console.WriteLine(dataReader.GetString(0));
}
}
现在只需修改具体工厂即可实现数据提供程序的转换,程序与数据提供器的解耦正是通过抽象工厂模式完美实现。