抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
以下给出抽象工厂方法模式的UML图:
回到《大话设计模式》里面的双数据库访问的例子:
namespace ConsoleApplication1 { 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 _deptNmae { get { return _deptNmae; } set { _deptNmae = value; } } } //IUser接口,用于客户端访问,解除与具体数据库访问的耦合 interface IUser { void Insert(User user); User GetUser(int id); } class SqlserverUser : IUser { public void Insert(User user) { Console.WriteLine("Sql Server添加一条记录"); } public User GetUser(int id) { Console.WriteLine("Sql Server根据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(); } //SQLServer实例化工厂,负责实例化SqlserverUser和SqlserverDepartment class SqlServerFactory : IFactory { public IUser CreateUser() { return new SqlserverUser(); } public IDepartment CreateDepartment() { return new SqlserverDepartment(); } } //Access实例化工厂,负责实例化AccessUser和AccessDepartment class AccessFactory : IFactory { public IUser CreateUser() { return new AccessUser(); } public IDepartment CreateDepartment() { return new AccessDepartment(); } } class Program { static void Main(string[] args) { IFactory factory = new SqlServerFactory(); IUser iu = factory.CreateUser(); //获得数据库User访问类的对象 iu.Insert(new User()); iu.GetUser(1); IDepartment idepartment = factory.CreateDepartment(); //获得数据库Department访问类的对象 idepartment.Insert(new Department()); idepartment.GetDepartment(1); Console.ReadKey(); } } }
其实以上代码就叫做抽象工厂模式了,当只有一个User表的时候,就叫工厂方法模式。当有了多个表了,就叫抽象工厂模式。但是以上代码是非常不好的,为什么?例如当你还要增加一个Project表的时候,就需要增加三个类,IProject、SqlserverProject、AccessProject,同时还需要更改IFactory、SqlserverFactory、AccessFactory。增加一张表就要更改三个类,这个是比较悲剧的。
因此,以下给出一个用简单工厂模式来优化抽象工厂模式的代码:
namespace ConsoleApplication1 { 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 _deptNmae { get { return _deptNmae; } set { _deptNmae = value; } } } //IUser接口,用于客户端访问,解除与具体数据库访问的耦合 interface IUser { void Insert(User user); User GetUser(int id); } class SqlserverUser : IUser { public void Insert(User user) { Console.WriteLine("Sql Server添加一条记录"); } public User GetUser(int id) { Console.WriteLine("Sql Server根据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; } } class DataAccess { private static readonly string db = "SqlServer"; //事先就设置好的数据库 //private static readonly string db = "Access"; //也就是说,更改数据库改的是这里了 public static IUser CreateUser() //这是第一个User的简单工厂 { IUser result = null; switch (db) //根据db的设置实例化不同的User数据库访问对象 { case "SqlServer": result = new SqlserverUser(); break; case "Access": result = new AccessUser(); break; } return result; } public static IDepartment CreateDepartment() //这是Department的简单工厂 { 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) { IUser iu = DataAccess.CreateUser(); //获得数据库User访问类的对象,要改数据库就要改DataAccess里面的设置 iu.Insert(new User()); iu.GetUser(1); IDepartment idepartment = DataAccess.CreateDepartment(); //获得数据库Department访问类的对象,这里没有依赖SqlserverFactory或AccessFactory了 idepartment.Insert(new Department()); idepartment.GetDepartment(1); Console.ReadKey(); } } }
经过简单工厂模式优化之后,客户端就不再依赖于SqlserverFactory或AccessFactory了,带到了解耦的目的。如果要添加一个Project,只需要添加响应类,但改就只需要修改DataAccess就可以了(在DataAccess里面在添加一个简单工厂)。很明显者不是最终方案,因为简单工厂方法还是要修改,switch case。
以下还有通过反射实现的更加好的方案:
namespace ConsoleApplication1 { 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 _deptNmae { get { return _deptNmae; } set { _deptNmae = value; } } } //IUser接口,用于客户端访问,解除与具体数据库访问的耦合 interface IUser { void Insert(User user); User GetUser(int id); } class SqlserverUser : IUser { public void Insert(User user) { Console.WriteLine("Sql Server添加一条记录"); } public User GetUser(int id) { Console.WriteLine("Sql Server根据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; } } class DataAccess { private static readonly string AssemblyName = "ConsoleApplication1"; //字符串为程序集的名字 private static readonly string db = ConfigurationManager.AppSettings["DB"]; public static IUser CreateUser() { string className = AssemblyName + "." + db +"User"; //拼接SqlserverUser类或AccessUser类所在的位置 return (IUser)Assembly.Load(AssemblyName).CreateInstance(className); //反射创建实例 } public static IDepartment CreateDepartment() { string className = AssemblyName + "." + "SqlserverDepartment"; return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className); } } class Program { static void Main(string[] args) { IUser iu = DataAccess.CreateUser(); //获得数据库User访问类的对象,要改数据库就要改DataAccess里面的设置 iu.Insert(new User()); iu.GetUser(1); IDepartment idepartment = DataAccess.CreateDepartment(); //获得数据库Department访问类的对象 idepartment.Insert(new Department()); idepartment.GetDepartment(1); Console.ReadKey(); } } }
配置文件App.config代码:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DB" value="Access" /> </appSettings> </configuration>
以上就是反射+抽象工厂的方法, 还是非常强大的,强大到什么程度呢?例如上面的代码,要修改数据库访问对象例如将Sqlserver改为Access根本不需要改动程序重新编译,只需要改动App.config就可以了。其实这里的反射只是改进了简单工厂模式而已,与抽象工厂没太大的关系。要记住,所有在用到简单工厂的地方都可以考虑用反射技术来去除switch解除分支判断所带来的耦合。
在以上的代码中,要增加一个Project表的话,只需要添加三个Project相关的类(扩展),再修改DataAccess,在其中增加一个CreateProject()方法就可以了。