主要参考《大话设计模式》
1. 引入
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场养动物、电视机厂生产电视等,然而,现实生活中,许多工厂是综合型工厂,能够生产各类产品,如大学包括各个系。
2. 定义
抽象工厂模式,为创建一组相关或相互依赖的对象提供一个接口,且无需指定它们的具体类。是所有形态的工厂模式中最为抽象和最具一般性的一种形态。
3. 场景实现
3.1 场景描述
在框架开发中,对于同一个ORM,假如刚开始开发时使用SQL Server数据库,但是也希望能够连接Access数据库,或其他的数据库(如MySql、Oracle等)。SQL Server在.net中使用的System.Data.SqlClient命名空间下的SqlConnection、SqlCommand、SqlParameter、SqlDataReader、SqlDataAdapter,而Access要用System.Data.Olede命名空间下的相应对象,我们不可能修改命名空间,或者重新写相同的业务代码只是修改与数据库相关的代码,这就是两倍的工作量。因而,针对类似问题,可以采用抽象工厂模式解决。
比如,现在要在业务逻辑代码相似的情况下,将SqlServer数据库改为使用Access数据库。 以“新增用户”和“获取用户”为例给出最基本的数据库访问程序。
3.2 最基本的数据访问程序
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 SqlServer { public void insert(User user) { Console.WriteLine("在sqlserver中为User表添加一条记录"); } public void select(int id) { Console.WriteLine("在sqlserver中根据用户id查找用户"); } } static void Main(string[] args) { User user = new User(); SqlServer sql = new SqlServer(); sql.insert(user); sql.select(1); Console.Read(); }
运行结果如下:
上述实现中,无法灵活的把sqlserver数据库替换成其他数据库,原因就是SqlServer sql = new SqlServer()使得sql对象被框死在Sqlserver上,如果此处是灵活的(多态的)应用,在执行sql.insert(user)时,不需要关注数据库到底是sqlserver还是access,因此,我们可以采用工厂方法模式进行封装。工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪个类。
3.3 使用工厂方法模式的数据访问程序
3.3.1 UML图
3.3.2 代码实现
class User { private int _id; public int ID { get { return _id;} set { this._id = value; } } private string _name; public string Name { get { return _name; } set { this._name = value; } } } interface IUser { void Insert(User user); User GeUser(int id); } class SqlserverUser:IUser { public void Insert(User user) { Console.WriteLine("在Sqlserver中新增一个用户"); } public User GeUser(int id) { Console.WriteLine("在sqlserver中获取一个用户"); return null; } } class AccessUser:IUser { public void Insert(User user) { Console.WriteLine("在Access中新增一个用户"); } public User GeUser(int id) { Console.WriteLine("在Access中获取一个用户"); return null; } } interface IFactory { IUser CreateUser(); } class SqlserverFactory:IFactory { public IUser CreateUser() { return new SqlserverUser(); } } class AccessFactory:IFactory { public IUser CreateUser() { return new AccessUser(); } } static void Main(string[] args) { User user=new User(); IFactory factory=new SqlserverFactory(); IUser sqlUser = factory.CreateUser(); sqlUser.Insert(user); sqlUser.GeUser(1); Console.WriteLine("Hello World!"); }
上述为使用工厂方法模式实现不同数据库中Insert、GetUser方法的实现,该实现中抽象接口与接口相关联,而不是与具体的类耦合,对代码进行了解耦,极大增加了代码的灵活性。在main方法中只需要根据自己的需求生成数据库实例即可,加入需要使用access数据库,只需main中修改为以下语句即可:
IFactory factory=new AccessFactory();
3.4 使用抽象工厂实现
上述用工厂方法实现不同数据库新增或获取User表数据,虽然实现了具体类与类之间的耦合,但一个数据库中会存在很多表,比如Department表,如果需要获取Department数据,则需要新增一个Department、ServerDepartment、AccessDepartment 3个类并更改IFactory、SqlServerFactory、AccssFactory
3.4.1 UML图
其中,IDepartment接口,用于客户端访问,解除具体数据库访问的耦合。
3.4.2 代码实现
//IDepartment接口,用于客户端访问,解除具体数据库访问的耦合。
interface IDepartment { void Insert(Department depement); User GeUser(int id); } class Department { private int _id; public int ID { get { return _id;} set { this._id = value; } } private string _name; public string Name { get { return _name; } set { this._name = value; } } }
//SqlserverDepartment类,用于访问sqlserver的Department class SqlserverDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在Sqlserver中新增一个部门"); } public Department GetDepartment(int id) { Console.WriteLine("在sqlserver中获取一个部门"); return null; } }
//AccessDepartment类,用于访问access数据库的Department class AccessDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在Access中新增一个部门"); } public Department GetDepartment(int id) { Console.WriteLine("在Access中获取一个部门"); return null; } }
interface IFactory { IUser CreateUser(); IDepartment CreateDepartment(); } // AccessFactory,实现IFactory接口,实例化AccessUser和AccessDepartment class AccessFactory:IFactory { public IUser CreateUser() { return new AccessUser(); } public IDepartment CreateDepartment() { return new AccessDepartment(); } } // SqlserverFactory类,实现IFactory接口,实例化SqlserverUser和SqlserverDepartment class SqlserverFactory:IFactory { public IUser CreateUser() { return new SqlserverUser(); } public IDepartment CreateDepartment() { return new SqlserverDepartment(); } } static void Main(string[] args) { User user=new User(); Department dep=new Department(); //IFactory factory=new SqlserverFactory(); IFactory factory=new AccessFactory(); IUser sqlUser = factory.CreateUser(); sqlUser.Insert(user); sqlUser.GeUser(1); IDepartment id = factory.CreateDepartment(); id.Insert(dep); id.GetDepartment(1); Console.WriteLine("Hello World!"); }
客户端只需确定实例化哪个数据库访问对象给factory。
只有一个User类和User操作类的时候,只需要工厂方法模式,但由于数据库中会有很多表,sqlserver和access又是两个不同的分类,因此,该问题中涉及多个产品系列的问题,对其实现,采用了抽象工厂模式。
4. 抽象工厂模式UML图
上图中,AbstractProductA和AbstractProductB是两个抽象产品,对应于第3.4节场景分析中的User类和Department类,它们又拥有自己不同的实现,因此,ProductA1、ProductA1、ProductB1、ProductB2就是对两个抽象产品具体分类的实现,分别代表SqlserverUser、AccessUser、SqlserverDepartment、AccessDepartment。.IFactory作为一个抽象工厂接口,应该包含实现创建产品的方法(如:CreateSqlServer,CreateAccess).而CreateFactory1和CreateFactory2则代表具体的工厂(如:SqlServerFactory,AccessFactory)。通常,会在客户端创建具体工厂类的实例,这个工厂再创建具有特定实现的产品对象,即为创建不同的产品对象,客户端应使用不同的具体工厂。
5. 其他应用场景
例如一个应用,需要在三个不同平台上运行:Windows、Linux、Android等,三个不同操作系统上的软件功能、应用逻辑、UI都应该是非常类似,唯一不同的是调用不同的工厂方法,由不同的产品类去处理与操作系统交互的信息。
6. 总结
抽象工厂模式的优点及缺点:
优点:
- (1)易于交换产品系列(如:上例中,使用SqlServer数据库还是Access数据库只需改变IFactury factory = new AccessFactory();/即可),只需改变具体工厂即可使用不同的产品配置
- (2)具体的创建实例过程与客户端分离。客户端是通过抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点:
产品的扩展困难,如上例产品中需要添加一张表Project,此时需要增加IProject 、SqlServerProject、AccessProject,并更改IFactory、SqlServerFactory、AccssFactory,改动较大。因此,在使用抽象工厂模式时,产品的结构等级结构划分非常重要。