抽象工厂模式
就拿数据库访问来讲:
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 SqlServerUser { public void Insert(User user) { Console.WriteLine("在Sqlserver中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Sqlserver中根据ID得到user表中一条记录"); return null; } }
User user = new User(); SqlServerUser su = new SqlServerUser(); su.Insert(user); su.GetUser(1);
这里我们发现不能更换数据库,因为SqlServerUser su = new SqlServerUser();将su框死在SqlServerUser上了,如果这里是灵活的,专业点的说法,这里是多态的,那么在执行Insert和GetUser的时候就不用考虑是Sql Server还是Access了。
可以使用工厂方法来封装new SqlServerUser();造成的变化【工厂方法模式定义一个创建对象的接口,让子类决定去实例化哪儿一个类】
工厂方法模式·使用:
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 AccessUser:IUser { public void Insert(User user) { Console.WriteLine("在Access中给User表增加一条记录"); } public User GetUser(int id) { Console.WriteLine("在Access中根据ID得到user表中一条记录"); return null; } }
interface IFactory { IUser CreatUser(); }
class SqlServerFactory:IFactory { public IUser CreatUser() { return new SqlServerUser(); } }
class AccessFactory:IFactory { public IUser CreatUser() { return new AccessUser(); } }
User user = new User(); IFactory factory = new SqlServerFactory(); IUser iu = factory.CreatUser(); iu.Insert(user); iu.GetUser(1);
现在如果要换数据库,只需要把new SqlServerFactory0改成new AccessFactory0,此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,却可以在运行时很
好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。”
但是数据库中总不能只有一个User表吧,肯定会有其他表,比如增加部门表。【Department表】
IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合。
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; } }
IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。
interface IFactory { IUser CreatUser(); IDepartment CreatDepartment();//新增 }
SqlServerFactory类,实现IFactory 接口,实例化SqlserverUser和SqlserverDepartment。
class SqlServerFactory : IFactory { public IUser CreatUser() { return new SqlServerUser(); } public IDepartment CreatDepartment() //新增 { return new SqlServerDepartment(); } }
AccessFactory类,实现IFactory接口,实例化AccessUser和AccessDepartment。
class AccessFactory:IFactory { public IUser CreatUser() { return new AccessUser(); } public IDepartment CreatDepartment() { return new AccessDepartment(); } }
User user = new User(); Department dept = new Department(); IFactory factory = new AccessFactory(); IUser iu = factory.CreatUser(); iu.Insert(user); iu.GetUser(1); IDepartment id = factory.CreatDepartment(); id.Insert(dept); id.GetDepartment(1);
这样就可以做到,只需更改IFactory factory = new AccessFactoryO为lIFactory factory = newSqlServerFactory0,就实现了数据库访问的切换了。”
此时重构出了一个非常重要的设计模式。”
“只有一个User类和User操作类的时候,是只需要工厂方法模式的,但现在显然你数据库中有很多的表,而SQL Server 与Access又是两大不同的分类,所以解决这种涉及到多个产品系列的问题,有一
个专门的工厂模式叫抽象工厂模式。”
抽象工厂模式:抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式的优点和缺点:
优点:“最大的好处便是易于交换产品系列,由于具体工厂类,例如IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,
它只需要改变具体工厂即可使用不同的产品配置。我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。
第二大好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
事实上,刚才写的例子,客户端所认识的只有IUser和IDepartment,至于它是用SQL Server来实现还是Access来实现就不知道了。”将开放-封闭原则,依赖倒转原则发挥到了极致。
缺点:假如,再增加一个表IPoject,那么就要增加三个类IProject、 SqlserverProject、 AccessProject, 还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。要改三个类,这太糟糕了。”
而且客户端程序类又不会只有一个,有很多地方都在使用IUser和IDepartment这样的设计在每个类开始都要声明 IFactory factory = new AccessFactory();这样的代码才行,不能解决改变数据库访问只改变一处的要求。
用简单工厂改进抽象工厂
首先去掉IFactory,SqlServerFactory,AccessFactory,增加DateAccess类
class DateAccess { 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; } }
User user = new User(); Department dept = new Department(); IUser iu = DateAccess.CreateUser(); iu.Insert(user); iu.GetUser(1); IDepartment id = DateAccess.CreateDepartment(); id.Insert(dept); id.GetDepartment(1);
使用反射+抽象工厂的数据访问程序
“我们要考虑的就是可不可以不在程序里写明‘ 如果是Sqlserver 就去实例化SQL Server数据库相关类,如果是Access就去实例化Access相关类’这样的语句,而是根据字符串db的值去某个地方找应该要实例化的类是哪一一个。
这样,我们的switch就可以对它说再见了。” 什么叫‘ 去某个地方找应该要实例化的类是哪一一个 就是一 种编程方式:依赖注入(Dependency Injection), 从字面上不太好理解,我们也不去管它。
关键在于如何去用这种方法来解决我们的switch问题。本来依赖注入是需要专门的IoC容器提供,比如Spring.NET,显然当前这个程序不需要这么麻烦,你只需要再了解一一个简单的.NET技术‘反射’就可以了。”