抽象工厂模式
一、 抽象工厂模式(Abstract Factory)模式
抽象工厂模式(Abstract Factory)定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体的类.
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向Client端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据LSP原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
二、 抽象工厂模式的角色与结构:
抽象工厂(Abstract Factory)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统商业逻辑无关的。
基本代码:
abstract class AbstractFactory { public abstract AbstractProductA CreateProductA(); public abstract AbstractProductB CreateProductB(); } class ConcreteFactory1 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA1(); } public override AbstractProductB CreateProductB() { return new ProductB1(); } } class ConcreteFactory2 : AbstractFactory { public override AbstractProductA CreateProductA() { return new ProductA2(); } public override AbstractProductB CreateProductB() { return new ProductB2(); } } abstract class AbstractProductA { } abstract class AbstractProductB { public abstract void Interact(AbstractProductA a); } class ProductA1 : AbstractProductA { } class ProductB1 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine(this.GetType().Name + " interacts with " + a.GetType().Name); } } class ProductA2 : AbstractProductA { } class ProductB2 : AbstractProductB { public override void Interact(AbstractProductA a) { Console.WriteLine(this.GetType().Name + " interacts with " + a.GetType().Name); } } class Client { private AbstractProductA AbstractProductA; private AbstractProductB AbstractProductB; // Constructor public Client(AbstractFactory factory) { AbstractProductB = factory.CreateProductB(); AbstractProductA = factory.CreateProductA(); } public void Run() { AbstractProductB.Interact(AbstractProductA); } }
三、抽象工厂模式与工厂方法模式的区别
工厂模式:定义一个用于创建对象的借口,让子类决定实例化哪一个类
抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类
它们区别在于:如果产品单一的时候,最合适是工厂模式,但如果有多个业务品种,业务分类时,通过抽象工厂模式产生需要的对象 是一种非常好的解决方式. 工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。
看一下工厂方法与抽象工厂的对比:
工厂方法模式 |
抽象工厂模式 |
针对的是一个产品等级结构 | 针对的是面向多个产品等级结构 |
一个抽象产品类 | 多个抽象产品类 |
可以派生出多个具体产品类 | 每个抽象产品类可以派生出多个具体产品类 |
一个抽象工厂类,可以派生出多个具体工厂类 | 一个抽象工厂类,可以派生出多个具体工厂类 |
每个具体工厂类只能创建一个具体产品类的实例 | 每个具体工厂类可以创建多个具体产品类的实例 |
四、抽象工厂模式的实例
下面用个实例来说明一下抽象工厂模式。
项目需要换DataBase,估计很多人也遇到过,但由于项目设计考虑不足,而导致更换database后,引起一系列的程序操作数据库问题,确实很让人头疼。而且网上也有很多人把这个更换database的作为抽象工厂模式的实例。刚好不巧,偶也中过枪。在之前做的项目中,有一个项目原来是用SQLServer数据库的,但后来客户说需要更换成Oracle数据库。好吧,那我们就修改吧,客户是老大。在整个修改过程中,工作量倒不是很大,主要是修改一些Oracle与SqlServer之间语法的转换(PL/SQL与T_SQL的区别挺大),修改工作还是比较顺利,当时自己也没有怎么在意这个工作过程。后来,当我自己去学习设计模式的时候,才发现如果当初那项目的设计比较糟糕的话,那么更换DB所带的工作是相当大的。呵呵,还好当初设计那项目的前辈考虑比较周全,也方便了我们后来的修改,维护工作。
假设数据库有Users与Department两个表需要查询以及插入一条纪录,用抽象工厂实现如下:
using System; using System.Collections.Generic; using System.Text; namespace 抽象工厂模式 { class Program { static void Main(string[] args) { User user = new User(); Department dept = new Department(); //AbstractFactory factory = new SqlServerFactory(); IFactory factory = new OracleFactory(); 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 OracleUser : IUser { public void Insert(User user) { Console.WriteLine("在Oracle中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Oracle中根据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 OracleDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在Oracle中给Department表增加一条记录"); } public Department GetDepartment(int id) { Console.WriteLine("在Oracle中根据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 OracleFactory : IFactory { public IUser CreateUser() { return new OracleUser(); } public IDepartment CreateDepartment() { return new OracleDepartment(); } } }
这样对于客户端调用来说,不是需要知道是SqlServer还是Oracle,只需要通过抽象操作实例,产口具体类名也被具体工厂的实现分离,不会出现在客户代码中,符合开放-封闭
的原则。 不过,这种设计也是有缺点的,比如需要添加一个新的表Project的时候,那么就需要添加三个类,IProject,SqlServerProject,OracleProject,而且还需要修改IFactory,SqlserverFactory,OracleFactory才能实现,这样修改动作也是比较大的。如果项目有许多调用数据库的类,那么要修改的地方就相当的多了。
那样需要对此设计再作优化,通过用简单工厂来优化抽象工厂,去除IFactory,SqlserverFactory,OracleFactory三个工厂类,然后用一个DataAccess类实现,用一个简单工厂模式去实现。 代码如下:
namespace 抽象工厂 { class Program { static void Main(string[] args) { Users users = new Users(); Department dept = new Department(); IUsers Iuser = DataAccess.CreateUser(); IDepartment Idept = DataAccess.CreateDepartment(); Iuser.Insert(users); Iuser.GetUser(10); Idept.InsertDept(dept); Idept.GetDepartment(11); Console.Read(); } } class Users { private int _UserID; public int UserID { get { return _UserID; } set { _UserID = value; } } private string _UserName; public string UserName { get { return _UserName; } set { _UserName = 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 IUsers { void Insert(Users user); Users GetUser(int Id); } interface IDepartment { void InsertDept(Department department); Department GetDepartment(int Id); } class OracleUser:IUsers { public void Insert(Users user) { Console.WriteLine("插入Oracle中的users表一条数据"); } public Users GetUser(int ID) { Console.WriteLine("从Oracle中的userts表中获取一条用户信息"); return null; } } class SqlServerUser : IUsers { public void Insert(Users user) { Console.WriteLine("插入SqlServer中的users表一条数据"); } public Users GetUser(int ID) { Console.WriteLine("从SqlServer中的userts表中获取一条用户信息"); return null; } } class OracleDepatment:IDepartment { public void InsertDept(Department dept) { Console.WriteLine("插入Oracle中的Department表一条数据"); } public Department GetDepartment(int Id) { Console.WriteLine("从Oracle中的Department表中根据ID获取一条部门信息"); return null; } } class SqlServerDepatment : IDepartment { public void InsertDept(Department dept) { Console.WriteLine("插入SqlServer中的Department表一条数据"); } public Department GetDepartment(int Id) { Console.WriteLine("从SqlServer中的Department表中根据ID获取一条部门信息"); return null; } } class DataAccess { private static readonly string db = "Sqlserver"; //private static readonly string db = "Oracle"; public static IUsers CreateUser() { IUsers result = null; switch (db) { case "Sqlserver": result = new SqlServerUser(); break; case "Oracle": result = new OracleUser(); break; } return result; } public static IDepartment CreateDepartment() { IDepartment result = null; switch (db) { case "Sqlserver": result = new SqlServerDepatment(); break; case "Oracle": result = new OracleDepatment(); break; } return result; } } }
这样一来,DataAccess类代替了IFactory,SqlserverFactory,OracleFactory三个工厂类,因为在DataAccess类中已经设置了db的值,所以简单工厂就不再需要输入参数,
客户端只需要通过DataAccess来创建数据库访问实例类,无必出现任何一个Sqlserver或者Oracle的字样,从而达到解耦的目的。
因为DataAccess中存在switch-case来判断当前db是Sqlserver还是Oracle中,当如果项目变动,需要把数据库修改成db2 或者access时,这样DataAccess类中的每个switch-case方法都修改,添加对应的数据库。 这样还是比较麻烦的。不过,在switch-case中,是通过字符串去实例化对应的数据库,如果能通过字符串找到应该实例化的类是那
一个,就可以去除switch了。这样的话,通过反射的方式对于DataAccess作进一步优化。
代码如下:
namespace 抽象工厂模式 { 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(); } } 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 OracleUser : IUser { public void Insert(User user) { Console.WriteLine("在Oracle中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Oracle中根据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 OracleDepartment : IDepartment { public void Insert(Department department) { Console.WriteLine("在Oracle中给Department表增加一条记录"); } public Department GetDepartment(int id) { Console.WriteLine("在Oracle中根据ID得到Department表一条记录"); return null; } } class DataAccess { private static readonly string AssemblyName = "抽象工厂模式"; private static readonly string db = "Sqlserver"; //private static readonly string db = "Oracle"; 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); } } }
还有,因为DataAccess中的db字符串是写死,如果写在配置文件里面,那样程序会更加灵活,DataAccess类就不用因为数据库变动而需要修改。
五、抽象工厂模式的优缺点
1) 分离了具体的类 Abstract Factory模式帮助你控制一个应用创建的对象的类。因为一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。客户通过它们的抽象接口操纵实例。产品的类名也在具体工厂的实现中被分离;它们不出现在客户代码中。
2) 使得易于交换产品系列 一个具体工厂类在一个应用中仅出现一次—即在它初始化的时候。这使得改变一个应用的具体工厂变得很容易。它只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列,所以整个产品系列会立刻改变
3) 有利于产品的一致性 当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要。而A b s t r a c t F a c t o r y很容易实现这一点。
4) 难以支持新种类的产品 难以扩展抽象工厂以生产新种类的产品。这是因为A b s t r a c t F a c t o r y接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口,这将涉及A b s t r a c t F a c t o r y类及其所有子类的改变。 (可以通过简单工厂改造,实现支持新种类的产品)。