大话设计模式十三之抽象工厂模式

菜鸟程序员碰到问题,只会用时间来摆平

工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。

一、基本的数据访问程序
用户类

SqlserverUser类---用于操作User表,假设只有“新增用户”和“得到用户”方法,其余方法以及具体的SQL语句省略


客户端代码
这里之所以不能换数据库,原因就在于SqlserverUser su = new SqlserverUser()使得su这个对象被框死在SQL Server上了。如果这里是灵活的,专业点的说法,是多态的,那么在指向'su.Insert(user);'和‘su.GetUser(1);’时就不用考虑是在用SQL server 还是在用Access。

用‘工厂方法模式’来封装new SqlserverUser()所造成的变化。
工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。

用工厂方法模式的数据访问程序
代码结构图





现在如果要换数据库,只需要把new SqlServerFactory()换成new AccessFactory(),此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,
却可以在运行时很好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。
但是代码里还是有指明‘new SqlServerFactory()’,要改的地方,依然很多。
数据库里不可能只有一个User表,很可能有其他表,比如增加部门表(Department表),此时要增加好多类了。
多写些类没有关系,只要增加灵活性就行。


二、用抽象工厂模式的数据访问程序

IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合。

SqlserverDepartment类,用于访问SQL Server的Department

AccessDepartment类,用于访问Access的Department

IFactory接口,定义一个创建访问User表对象的抽象的工厂接口


SqlServerFactory类,实现IFactory接口,实例化SqlserverUser和SqlserverDepartment


AccessFactory类,实现了IFactory接口,实例化AccessUser和AccessDepartment


这样就可以做到,只需要更改IFactory factory = new AccessFactory()为IFactory factory = new SqlServerFactory(),就实现了数据库访问的切换了。
只有一个User类和User操作类的时候,是只需要工厂方法模式的,但现在显然你数据库中有很多的表,而SQL Server和Access又是两大不同的分类,
所以解决这种涉及到多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式。

抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。


AbstractProductA和AbstractProductB是两个抽象产品。
之所以是抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,
ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,
比如ProductA1可以理解为是SqlserverUser,而ProductB1是AccessUser。
IFactory是一个抽象工厂接口,它里面包含所有的产品创建的抽象方法。
ConcreateFactory1和ConcreteFactory2就是具体的工厂了。
就像SqlserverFactory和AccessFactory一样。

通常是在运行时刻再创建一个ConcreteFactory的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。

三、抽象工厂模式的优点与缺点
优点:
最大的好处便是易于交换产品系列,由于具体工厂类,(例如IFactory factory=new AccessFactory())在一个应用中只需要在初始化的时候出现一次,
这就使得改变一个应用的具体工厂变得非常容易它只需要改变具体工厂即可使用不同的产品配置(我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小)。
第二个好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
(这个模式很好地体现了开放-封闭原则、依赖倒转原则。是个模式都是会有缺点的,都有不适用的时候,要辩证地看待问题。)

如果需求来自增加功能,比如我们现在要增加项目表Project,那就至少要增加三个类,IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。
客户端程序类显然不会是只有一个,有很多地方都在使用IUser或IDepartment,而这样的设计,其实在每一个类的开始都需要声明IFactory factory=new SqlserverFactory(),如果有100个调用数据库访问类,
就要更改100次IFactory factory=new AccessFactory()这样的代码才行。这不能解决要更改数据库访问时,改动一处就完全更改的要求。


编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。




用简单工厂来改进抽象工厂


这里与其用那么多工厂类,不然直接用一个简单工厂来实现,抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,
由于事先设置了db的值(SqlserverFactory和AccessFactory),所以简单工厂的方法都不需要输入参数,这样在客户端就只需要DataAccess.CreateUser()和DataAccess.CreateDepartment()来生成具体的数据库访问类实例,
客户端没有出现任何一个SQL Server或Access的字样,达到了解耦的目的。

这样的改进确实是比之前的代码要更进一步了,客户端已经不再受改动数据库访问的影响了,但是如果需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就比较麻烦了。
需要在DataAccess类中每个方法的swich中加case了。

四、用反射+抽象工厂的数据访问程序
其实要考虑的就是可不可以不在程序里写明‘如果是Sqlserver就去实例化SQL Server数据库相关类,如果是Access就去实例化Access相关类’这样的语句,而是根据字符串db的值去某个地方找应该实例化的类是哪一个。这样,就可以对switch说再见了。
这是一种编程方式:依赖注入(Dependency Injection)。

反射技术使用的格式:
Assembly.Load("程序集名称").CreateInstance("命名空间.类名称")

只要在程序顶端写上using System.Reflection;来引用Reflection,就可以使用反射类帮我们客户抽象工厂模式的先天不足了。


实例化的效果是一样的,常规方法是写明了要实例化SqlserverUser对象。反射的写法,其实也指明了要实例化SqlserverUser对象,但是常规放不可以灵活更换为AccessUser,这都是事先编译号的代码。
在反射中,因为是字符串,可以用变量来处理,也就可以根据需要更换。
准确地说,是将程序由编译时转为运行时。由于'CreateInstance("抽象工厂模式.SqlserverUser")'中的字符串是可以携程变量的,而变量的值到底是SQL Server,还是Access完全可以由事先的哪个db变量来决定。所以就去除了switch判断的麻烦。


现在如果增加了Oracle数据访问,相关的类的增加是不可避免的,这点无论我们用任何办法都解决不了,不过这叫扩展,开放-封闭原则性告诉我们,对于扩展,我们开放。
但对于修改,我们应该要尽量关闭,就目前而言,我们只需要更改private static readonly string db = "Sqlserver";为private static readonly string db = "Oracle";也就
意味着(IUser)Assembly.Load(AssemblyName).CreateInstance(className);这一句发生了变化

这样的结果就是DataAccess.CreateUser()本来得到的是SqlserverUser的实例,而现在编程了OracleUser的实例了。
如果我们需要增加Project产品时,只需要增加三个与Project相关的类,再修改DataAccess,在其中增加一个public static IProject CreateProject()方法就可以了。
这代码漂亮多了,但是,总感觉还是由点缺憾,因为在更换数据库访问时,我们还是要去改程序(改db这个字符串的值)重编译,如果可以不改程序,哪才是真正符合开放-封闭原则。

五、用反射+配置文件实现数据访问程序
可以读文件来给DB字符串赋值,在配置文件中写明是Sqlserver还是Access,这样就连DataAccess类也不用更改了。

用反射+抽象工厂模式解决了数据库访问时的可维护可扩展问题。
所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。
switch或者if是程序里的号东西,但在应对变化上却显得老态龙钟。反射技术的确可以很好地解决它们难以应对变化,难以维护和扩展的诟病。
一个程序员如果从来没有熬夜写程序的经历,不能算是一个号程序员,因为他没有痴迷过,所以他不会由大成就。
无痴迷,不成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值