抽象工厂模式-学习笔记
学习抽象工厂模式ing...
希望大家给出意见,有不对的地方大家多多指教!小女子在此谢过!
---------------------------------------------------------------------------
最基本的数据访问程序
User类:表示数据库中字段,假设只有ID和Name两个字段
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; } } }
SqlserverUser类:用于操作User表,假设只有“添加用户”和“得到用户”两个方法(这里为了简单,使用两句话代替O(∩_∩)O~)
class SqlserverUser { public void Insert(User user) { Console.WriteLine("通过SQL Server给User表添加一条记录"); } public User GetUser(int id) { Console.WriteLine("通过SQL Server根据ID得到User表中的一条记录"); return null; }
客户端代码
static void Main(string[] args) { User user = new User(); SqlserverUser su = new SqlserverUser(); su.Insert(user); su.GetUser(1); Console.ReadKey(); }
在这个最基本数据访问程序中,如果要更换数据库,那么如果代码量非常大的情况下,修改数据库代码是非常痛苦的,甚至与数据库无关的代码页需要改。这里之所以不能更换数据库,原因在于
SqlserverUser su = new SqlserverUser();
使得su这个对象被框死在SQL Server上了,如果这里是多态的,那么在执行
su.Insert(user); su.GetUser(1);
时,就不用考虑是在用SQL Server还是在用Access或者Oracle了。
使用工厂方法模式的数据访问程序,解除客户端与具体数据库访问的耦合
这里我们添加一个IUser接口,用来解除客户端与具体数据库访问的耦合:
interface IUser { void Insert(User user); User GetUser(int id); }
这个时候我们就需要修改原先简单的数据访问代码了,因为我们的目的是解除与具体数据访问的耦合,所以需要让数据访问类来继承这个接口(不管是SQL Server还是Access),然后通过工厂模式来创建访问数据库表User的对象。
首先修改SqlserverUser类,让之继承IUser接口;
class SqlserverUser:IUser
然后创建AccessUser类,用来访问Access数据库中的User类
class AccessUser:IUser { public void Insert(User user) { Console.WriteLine("通过Access给User表添加一条记录"); } public User GetUser(int id) { Console.WriteLine("通过Access根据ID得到User表中的一条记录"); return null; } }
接下来创建IFactory接口,定义一个创建访问User表对象的抽象的工厂接口
interface IFactory { IUser CreateUser(); }
SqlserverFactory类,实现IFactory接口,用来实例化SqlserverUser
class SqlServerFactory:IFactory { public IUser CreateUser() { return new SqlserverUser(); } }
AccessFactory类,同样实现IFactory接口,用来实例化AccessUser
class AccessFactory:IFactory { public IUser CreateUser() { return new AccessUser(); } }
修改后的客户端代码:
static void Main(string[] args) { User user = new User(); //IFactory factory = new SqlServerFactory(); IFactory factory = new AccessFactory(); //如果需要修改数据库,只需要变更这句代码即可 IUser su = factory.CreateUser(); su.Insert(user); su.GetUser(1); Console.ReadKey(); }
此时由于多态的关系,使得声明IUser接口的对象su事先不知道是在访问哪个数据库,却可以再运行时很好地完成工作,这就解决了所谓的业务逻辑与数据访问的解耦
这个时候问题还没有完全解决,数据库中不可能只有一张User表,如果增加一张Department表,那么依照上面的方法,需要增加一个IDepartment接口;
然后由AccessDepartment和SqlServerDepartment两个类来实现这个接口,完成对数据库的操作。
完成对数据库的操作之后,就需要在对应的数据库工厂中创建相应的对象,因为所有的数据库工厂都实现了IFactory接口,所以我们只需要在对应的数据库工厂中添加相应的方法。
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(); } }
这样一来虽然解决了业务逻辑和数据访问的紧耦合问题,但是如果数据库中的数据量非常庞大的时候,我们也要一张表一个接口的这样做吗?当然不,当只有一个User表的时候,只需要一个User类和User操作类,这时使用工厂方法模式来解决问题;但是现在一般情况下显然数据库中有很多的表,而且SQL Server和Access又是两大不同的分类,所以解决涉及到多个产品系列的问题,就有一种专门的工厂模式:抽象工厂模式。
抽象工厂模式
Abstract Factory,提供一个创建一系列相关或相互依赖的接口,而无需指定它们具体的类。
优点:
1、便于交换产品系列,由于具体工厂类,比如IFactory factory = new SqlServerFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变的非常容易,它只需要改变具体工厂即可使用不同的产品配置;
2、它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。
缺点:
1、如果有增加功能的需求,比如增加表Project,那么至少需要增加三个类:IProject、SqlServerProject、AccessProject,同时还需要更改IFactory、SqlServerFactory、AccessFactory才能完全实现。所以增加功能时是非常麻烦的。
2、客户端程序多的情况下(在这里是Main里面的代码),在每一个类的开始都要声明IFactory factory = new SqlServerFactory(),如果有100个调用数据库访问的类,改为iAccess时就需要修改至少100处代码。
为了解决上面缺点2的问题,我们试着用简单工厂来代替抽象工厂;
不再使用IFactory、SqlServerFactory、AccessFactory,替换为DataAccess,用一个简单工厂来实现:
class DataAccess { //private static readonly string db = "SqlServer"; private static readonly 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; } }
客户端代码:
static void Main(string[] args) { User user = new User(); Department dept = new Department(); //IFactory factory = new SqlServerFactory(); //IFactory factory = new AccessFactory(); //IUser su = factory.CreateUser(); IUser su = DataAccess.CreateUser(); su.Insert(user); su.GetUser(1); //IDepartment id = factory.CreateDepartment(); IDepartment id = DataAccess.CreateDepartment(); id.Insert(dept); id.GetDepartment(1); Console.ReadKey(); }
通过上面的方法在客户端没有出现一个SQL Server或者Access的字样,达到了解耦的目的。
但是如果需要使用Oracle数据库访问,就需要在每个switch中增加case了。
用反射+配置文件+抽象工厂实现数据访问程序
在增加数据库时,相关类的增加是不可避免的,这点目前为止还不能解决,不过开放-封闭原则告诉我们:对于扩展,我们开放;对于修改,我们应该尽量关闭。
因此我们可以通过反射,来获得当前运行的程序集名称,再通过拼接字符串得到所需要创建的实例的类名,代码如下:
private static readonly string db = ConfigurationManager.AppSettings["DB"]; private static readonly string assemblyName = Assembly.GetExecutingAssembly().GetName().Name; 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); }
其中我把相关的数据库字符串放在了配置文件中:
<appSettings> <add key="DB" value="Access"/> </appSettings>
这样就解决了关于抽象工厂数据访问的可维护可扩展的问题。
--------------------------------------------------------------------
各位多多指导...Thank you!