前言
我们先来看一段基本的数据访问代码,以‘新增用户’和得到用户为例,假设只有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表
public class SqlserverUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } }
客户端代码
static void Main(string[] args) { User user = new User(); SqlserverUser su = new SqlserverUser();//此处与SQL Server耦合 su.Insert(user);//插入用户 su.GetUser(1);//得到ID为1的用户 Console.Read(); }
这里和Sql Server数据库耦合,不能做到灵活的更换数据库,如果下次要换成Mysql或者其他数据库,就非常麻烦了。这里我们可以改进下程序,使用工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。
IUser接口,用于客户端访问,解除于具体数据库访问的耦合
interface IUser { void Insert(User user); User GetUser(int id); }
SqlserverUser类,用于访问SQL Server的User
public class SqlserverUser:IUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } }
AccessUser类,用于访问Access的User
class AccessUser : IUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } }
IFactory 接口,定义一个创建访问User表对象的抽象工厂接口
/// <summary> /// 创建访问User表对象的抽象工厂接口 /// </summary> interface IFactory { IUser CreateUser(); }
SqlServerFactory类,实现IFactory接口,实例化SqlserverUser
/// <summary> /// 实现IFactory接口,实例化SqlserverUser /// </summary> class SqlServerFactory : IFactory { public IUser CreateUser() { return new SqlserverUser(); } }
AccessFactory类,实现IFactory接口,实例化AccessUser
/// <summary> /// 实现IFactory接口,实例化AccessUser /// </summary> /// <returns></returns> class AccessFactory : IFactory { public IUser CreateUser() { return new AccessUser(); } }
客户端代码
static void Main(string[] args) { User user = new User(); //若要改成Access数据库,只需要将本剧改成 IFactory factory = new AccessFactory(); IFactory factory = new SqlServerFactory(); IUser iu = factory.CreateUser(); iu.Insert(user); iu.GetUser(1); Console.Read(); }
程序到这里依然还存在问题,虽然我们把业务逻辑和数据访问解耦了,但是如果我们数据此时新增其他的表,比如部门表(Department),此时程序应该怎样才会更灵活呢?思考五秒。。。。。
代码结构图如下
IDepartment接口,用于客户端访问,解除于具体数据库访问的耦合
interface IDepartment { void Insert(IDepartment department); Department GetDepartment(int id); }
SqlserverDepartment类,用于访问SQL server 的Department.
class SqlserverDepartment:IDepartment { public void Insert(Department user) { Console.WriteLine("在Sql Server中给Department表增加一条记录"); } public Department GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到Department表一条记录"); return null; } }
AccessDepartment类,用于访问Access的Department
class AccessDepartment : IDepartment { public void Insert(Department user) { Console.WriteLine("在Sql Server中给Department表增加一条记录"); } public Department GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到Department表一条记录"); return null; } }
IFactory接口,定义一个创建访问Department表对象的抽象工厂接口。
/// <summary> /// 创建访问表对象的抽象工厂接口 /// </summary> interface IFactory { IUser CreateUser(); IDepartment CreateDepartment();//增加接口方法 }
SqlServerFactory类,实现IFactory接口,实例化SqlServerDepartment和SqlServerUser
/// <summary> /// 实现IFactory接口,实例化SqlserverUser /// </summary> class SqlServerFactory : IFactory { public IUser CreateUser() { return new SqlserverUser(); } /// <summary> /// 新增SqlserverDepartment工厂 /// </summary> /// <returns></returns> public IDepartment CreateDepartment() { return new SqlserverDepartment(); } }
AccessFactory类,实现了IFactory接口,实例化User和Department
/// <summary> /// 实现IFactory接口,实例化AccessUser /// </summary> /// <returns></returns> class AccessFactory : IFactory { public IUser CreateUser() { return new AccessUser(); } /// <summary> /// 新增OleDBDepartment工厂 /// </summary> /// <returns></returns> public IDepartment CreateDepartment() { return new AccessDepartment(); } }
客户端代码
static void Main(string[] args) { User user = new User(); Department dept = new Department(); //只需确定实例化哪一个数据库访问对象给factory //IFactory factory = new SqlServerFactory(); IFactory factory = new AccessFactory(); //则此时已于具体的数据库访问接触了依赖 IUser iu = factory.CreateUser(); iu.Insert(user); iu.GetUser(1); //则此时已于具体的数据库访问接触了依赖 IDepartment id = factory.CreateDepartment(); id.Insert(dept); id.GetDepartment(1); Console.Read(); }
此时,我们会发现数据库中有很多个表,和SQL Server和Access又是两大不同的分类,所以解决这种涉及到多产品系列的问题,我们可以使用抽象工厂模式。
抽象工厂模式
AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为他们都有可能有两种不同的实现。就上面的例子来说就是User和Department,而ProductA1,ProductA2和ProductB1,ProductB2就是对两个抽象产品的具体分类实现 ,比如ProductA1可以理解是SqlserverUser,而ProductB1是AccessUser。
IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法,而ConcreateFactory1和ConcreateFactory2就是具体的工厂了,就像 SqlserverFactory和AccessFactory一样。
这样做优点和缺点?
好处:易于交换产品系列,由于是具体工厂类,例如IFactory factory=new AccessFactory,再一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂边的非常容易,它只需要改变具体工厂即可使用不同的产品配置。
第二大好处是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点:很明显,我们新增一个表改动的地方很多,接口、工厂类,具体实现,这太糟糕了
用简单工厂来改进抽象工厂
去除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 CreateUser() { IDepartment result = null; switch (db) { case "Sqlserver": result = new SqlserverDepartment(); break; case "Access": result = new AccessDepartment(); break; } return result; } }
由于db的实现设置,所以swtich中可以根据选择实例化出相应的对象
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(); }
这里用简单工厂来实现了,我们抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,客户端没有出现任何一个Sqlserver 和Access的字样,达到了解耦的目的。
不过此时还不是最完美的,因为我们需要增加Oracle的话,现在需要在DataAccess类中每个方法的swicth中加case了。
反射+抽象工厂
上述问题的关键在于我们如何去解决switch的问题,可以使用依赖注入(Dependency Injection),本来依赖注入需要专门的IoC容器提供,比如:Spring.NET,显然这个程序不需要这么麻烦。
程序引用:using System.Reflection 就可以使用反射帮我们克服抽象工厂模式的先天不足了。
DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory.
class DataAccess { private static readonly string AssemblyName = "程序集名称"; private static readonly string db = "Sqlserver";//数据库名称,可以替换成Access public static IUser CreateUser() { string className = AssemblyName + "." + db + "User"; return (IUser)Assembly.Load(AssemblyName).CreateInstance(className); } public static IDepartment CreateDepartment() { string className = AssemblyName + "." + db + "User"; return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className); } }
用反射+配置文件实现数据库访问
最后得到执行结果:
总结:所有简单工厂的地方,都可以考虑用反射技术取出swtich或if,接触分支判断带来的耦合。
- 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载!
- 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。
- 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入
- 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。
- 我的博客:http://www.cnblogs.com/zhangxiaoyong/