《大话设计模式》之--第15章 就不能不换DB吗?----抽象工厂模式

http://blog.csdn.net/monkey_d_meng/article/details/5707076


第15章 就不能不换DB吗?----抽象工厂模式

15.1就不能不换DB吗?

“这么晚才回来,都11点了。”大鸟看着刚推门而入的小菜问道。

“我了个去~没办法呀,工作忙。”小菜叹气说道。

“怎么会这么忙。加班有点过头了呀。”

“都是换数据库惹的祸叹。”

“怎么了?”

“我本来写好了一个项目,是给一家企业做的电子商务网站,是用SQLServer作为数据库的,应该说上线后除了开始有些小问题,基本都还可以。而后,公司接到另外一家公司类似需求的项目,但这家公司想省钱,租用了一个空间,只能用Access,不能用SQL Server,于是就要求我今天改造原来那个项目的代码。”

“哈哈,你的麻烦来了。”

“是呀,那是相当的麻烦。但开始我觉得很简单呀,因为地球人都知道,SQL Server和Access在ADO.NET上的使用是不同的,在SQL Server上用的是System.Data.SqlClient命名空间下的SqlConnectian、SqlCommand、SqlParameter、SqlDataReader、SqlDataAdapter,而Access则要用System.Data.OleDb命名空间下的相应对象,我以为只要做一个全体替换就可以了,哪知道,替换后,错误百出。”

“那是一定的,两者有不少不同的地方。你都找到了些什么问题?”

“实在是多呀。在插入数据时Access必须要insert into而SQL Server可以不用into的,SQL Server中的GeDate()在Access中没有,需要改成Now(),SQL Server中有字符串函数Substring,而Access中根本不能用,我找了很久才知道,可以用Mid,这好像是VB中的函数口”

“小菜还真犯了不少错呀,insert into这是标准语法,你干吗不加into,这是自找的麻烦。”

“这些问题也就罢了,最气人的是程序的登录代码,老是报错,我怎么也找不到出了什么问题,搞了几个小时口最后才知道,原来Access对一些关键字,例如password是不能作为数据库的字段的,如果密码的字段名是password,SQL Server中什么问题都没有,运行正常,在Access中就是报错,而且报得让人莫名其妙。”

“‘关键字’应该要用‘[’和‘]’包起来,不然当然是容易出错的。”

“就这样,今天加班到这时候才回来。”

“以后你还有的是班要加了。”

“为什么?”

“只要网站要维护,比如修改或增加一些功能,你就得改两个项目吧,至少在数据库中做改动。相应的程序代码都要改,甚至和数据库不相干的代码也要改,你既然有两个不同的版本,两倍的工作量也是必然的。”

“是呀,如果哪一天要用Oracle数据库,估计我要改动的地方更多了。”

“那是当然,Oracle的SQL语法与SQL Server的差别更大。你的改动将是空前的。”

“大鸟只会夸张,哪有这么严重,大不了再加两天班就什么都搞定了。”

“哼”,大鸟笑着摇了摇头,很不屑一顾,“菜鸟程序员碰到问颐,只会用时间来摆平,所以即使整天加班,老板也不想给菜鸟加工资,原因就在于此。”

“你什么意思嘛!”小菜气道,“我是菜鸟我怕谁。”接着又拉了拉大鸟,“那你说怎么搞定才是好呢?”

“知道求我啦,”大鸟端起架子,“教你可以,这一周的碗你洗。”

“行,”小菜很爽快地答应道,“在家洗碗也比加班熬夜强。”

15.2最基本的数据访问程序

“那你先写一段你原来的数据访问的做法给我看看。”

“那就用增加用户和得到用户为例吧。”

 

[java]  view plain copy
  1. //用户类假设只有ID和Name两个字段,其余省略。  
  2. public class User  
  3. {  
  4.     private int     id;  
  5.     private String  name;  
  6.   
  7.     public int getId()  
  8.     {  
  9.         return id;  
  10.     }  
  11.   
  12.     public void setId(int id)  
  13.     {  
  14.         this.id = id;  
  15.     }  
  16.   
  17.     public String getName()  
  18.     {  
  19.         return name;  
  20.     }  
  21.   
  22.     public void setName(String name)  
  23.     {  
  24.         this.name = name;  
  25.     }  
  26. }  
  27. //SqlServerUser类,用于操作User表,假设只有新增用户和得到用户的方法,其余方法以及具体SQL语句省略。  
  28. public class SqlServerUser  
  29. {  
  30.     public void insert(User user)  
  31.     {  
  32.         System.out.println("在SQL Server中给User表增加一条记录");  
  33.     }  
  34.   
  35.     public User getUser(int id)  
  36.     {  
  37.         System.out.println("在SQL Server中根据ID得到User表一条记录");  
  38.         return null;  
  39.     }  
  40. }  
  41. //客户端代码  
  42. public class Main  
  43. {  
  44.     public static void main(String[] args)  
  45.     {  
  46.         User user = new User();  
  47.   
  48.         SqlServerUser su = new SqlServerUser();  
  49.   
  50.         su.insert(user);  
  51.   
  52.         su.getUser(1);  
  53.     }  
  54. }  

“我最开始就是这样写的,非常简单。”

“这里之所以不能换数据库,原因就在于SqlServerUser su = new SqlServerUser()使得su这个对象被框死在了SQL Server上了。如果这里是灵活的,专业点的说法,是多态的,那么在执行su.insert(user)和su.getUser(1)时就不用考虑是在用SQL Server还是在用Access。”

“你的意思我明白,你是希望我用工厂方法模式来封装new SqlServerUser()所造成的变化?”

“小菜到了半夜,还是很清醒嘛,8错8错。工厂方法模式是定义一个用户创建对象的接口,让子类决定实例化哪个类,试试看吧。”

“中!”

15.3用了工厂方法模式的数据访问程序

小菜很快给出了工厂方法实现的代码。

代码结构图

 

[java]  view plain copy
  1. //IUser接口,用于客户端访问,解除与具体数据库访问的耦合  
  2. public interface IUser  
  3. {  
  4.     void insert(User user);  
  5.   
  6.     User getUser(int id);  
  7. }  
  8. //SqlServerUser类,用于访问SQL Server的User  
  9. public class SqlServerUser implements IUser  
  10. {  
  11.     public void insert(User user)  
  12.     {  
  13.         System.out.println("在SQL Server中给User表增加一条记录");  
  14.     }  
  15.   
  16.     public User getUser(int id)  
  17.     {  
  18.         System.out.println("在SQL Server中根据ID得到User表一条记录");  
  19.         return null;  
  20.     }  
  21. }  
  22. //AccessUser类,用于访问Access的User  
  23. public class AccessUser implements IUser  
  24. {  
  25.     public void insert(User user)  
  26.     {  
  27.         System.out.println("在Access中给User表增加一条记录");  
  28.     }  
  29.   
  30.     public User getUser(int id)  
  31.     {  
  32.         System.out.println("在Access中根据ID得到User表一条记录");  
  33.         return null;  
  34.     }  
  35. }  
  36. //IFactory接口,定义一个创建访问User表对象的抽象的工厂接口  
  37. public interface IFactory  
  38. {  
  39.     IUser createUser();  
  40. }  
  41. //SqlServerFactory类,实现IFactory接口,实例化SqlServerUser  
  42. public class SqlServerFactory implements IFactory  
  43. {  
  44.   
  45.     public IUser createUser()  
  46.     {  
  47.         return new SqlServerUser();  
  48.     }  
  49.   
  50. }  
  51. //AccessFactory类,实现IFactory接口,实例化AccessUser  
  52. public class AccessFactory implements IFactory  
  53. {  
  54.     public IUser createUser()  
  55.     {  
  56.         return new AccessUser();  
  57.     }  
  58. }  
  59. //客户端代码  
  60. public class Main  
  61. {  
  62.     public static void main(String[] args)  
  63.     {  
  64.         User user = new User();  
  65.   
  66.         IFactory factory = new SqlServerFactory();  
  67.   
  68.         IUser iu = factory.createUser();  
  69.   
  70.         iu.insert(user);  
  71.         iu.getUser(1);  
  72.     }  
  73. }  

“大鸟,来看看这样写成不?”

“非常好。现在如果要换数据库,只需要把new SqlServerFactory()改成new AccessFactory(),此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,却可以在运行时很好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。”

“但是,大鸟,这样写,代码里还是有指明new SqlServerFactory()啊,我要改的地方,依然很多。”

“这个先不急,待会再说,问题还没有完全解决,你的数据库里面不可能只有一个User表吧,很可能有其他表,比如增加部门表(Department表),此时如何办呢?”

“啊,我觉得那要增加好多的类了,我来试试看。”

“多写些类有什么关系,只要能增加灵活性,以后就不用加班了。小菜好好加油。”

15.4用了抽象工厂模式的数据访问程序

小菜再次修改代码,拉回了关于部门表的处理。

代码结构图

 

[java]  view plain copy
  1. //Department类  
  2. public class Department  
  3. {  
  4.     private int     id;  
  5.     private String  name;  
  6.   
  7.     public int getId()  
  8.     {  
  9.         return id;  
  10.     }  
  11.   
  12.     public void setId(int id)  
  13.     {  
  14.         this.id = id;  
  15.     }  
  16.   
  17.     public String getName()  
  18.     {  
  19.         return name;  
  20.     }  
  21.   
  22.     public void setName(String name)  
  23.     {  
  24.         this.name = name;  
  25.     }  
  26. }  
  27. //IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合  
  28. public interface IDepartment  
  29. {  
  30.     void insert(Department department);  
  31.   
  32.     Department getDepartment(int id);  
  33. }  
  34. //SqlServerDepartment类,用于访问SQL Server的Department  
  35. public class SqlServerDepartment implements IDepartment  
  36. {  
  37.     public void insert(Department department)  
  38.     {  
  39.         System.out.println("在SQL Server中给Deaprtment表增加一条记录");  
  40.     }  
  41.   
  42.     public Department getDepartment(int id)  
  43.     {  
  44.         System.out.println("在SQL Server中根据ID得到Deaprtment表一条记录");  
  45.         return null;  
  46.     }  
  47. }  
  48. //AccessDepartment类,用于访问Access的Department  
  49. public class AccessDepartment implements IDepartment  
  50. {  
  51.     public void insert(Department department)  
  52.     {  
  53.         System.out.println("在Access中给Deaprtment表增加一条记录");  
  54.     }  
  55.   
  56.     public Department getDepartment(int id)  
  57.     {  
  58.         System.out.println("在Access中根据ID得到Deaprtment表一条记录");  
  59.         return null;  
  60.     }  
  61. }  
  62. //IFactory接口,定义一个创建访问User表对象的抽象工厂接口  
  63. public interface IFactory  
  64. {  
  65.     IUser createUser();  
  66.   
  67.     IDepartment createDepartment();  
  68. }  
  69. //SqlServerFactory类,实现IFactory接口,实例化SqlServerUser和SqlServerDepartment  
  70. public class SqlServerFactory implements IFactory  
  71. {  
  72.     public IUser createUser()  
  73.     {  
  74.         return new SqlServerUser();  
  75.     }  
  76.   
  77.     public IDepartment createDepartment()  
  78.     {  
  79.         return new SqlServerDepartment();  
  80.     }  
  81. }  
  82. //AccessFactory类,实现IFactory接口,实例化AccessUser和AccessDepartment  
  83. public class AccessFactory implements IFactory  
  84. {  
  85.     public IUser createUser()  
  86.     {  
  87.         return new AccessUser();  
  88.     }  
  89.   
  90.     public IDepartment createDepartment()  
  91.     {  
  92.         return new AccessDepartment();  
  93.     }  
  94. }  
  95. //客户端代码  
  96. public class Main  
  97. {  
  98.     public static void main(String[] args)  
  99.     {  
  100.         User user = new User();  
  101.         Department department = new Department();  
  102.   
  103.         // IFactory factory = new SqlServerFactory();  
  104.           
  105.         IFactory factory = new AccessFactory();  
  106.   
  107.         IUser iu = factory.createUser();  
  108.   
  109.         iu.insert(user);  
  110.         iu.getUser(1);  
  111.   
  112.         IDepartment id = factory.createDepartment();  
  113.   
  114.         id.insert(department);  
  115.         id.getDepartment(1);  
  116.     }  
  117. }  
  118. 结果显示:  
  119. 在Access中给User表增加一条记录  
  120. 在Access中根据ID得到User表一条记录  
  121. 在Access中给Deaprtment表增加一条记录  
  122. 在Access中根据ID得到Deaprtment表一条记录  

“大鸟,这样就可以了,只需要改IFactory factory = new AccessFactory()为IFactory factory = new SqlServerFactory(),就可以实现了数据库访问的切换了。”

“很好嘛,实际上,在不知不觉间,你已经通过需求的不断演化,重构出了一个非常重要的设计模式。”

“这不就是刚才的工厂方法模式吗?”

“只有一个User类和User操作类的时候,是只需要工厂方法模式的,但现在显然你的数据库中有很多的表,而SQL Server与Access又是两大不同的分类,所以解决这种涉及到多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式。”

15.5抽象工厂模式

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

抽象工厂模式(Abstract Factory)结构图

“AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,比如ProductA1可以理解为是SqlServerUser,而ProductB1是AccessUser。”

“这么说,IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFacotry2就是具体的工厂了。就像SqlServerFactory和AccessFactory一样。”

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

15.6抽象工厂模式的优点和缺点

“这样做有虾米好处?”

“最大好处在于易于交换产品系列,由于具体工厂类,例如IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体的工厂就可以做到了。第二大好处在于,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。事实上,你刚才写的例子,客户端所认识的只有IUser和IDepartment,至于它是用SQL Server来实现还是用Access来实现就不知道了。”

“啊,我感觉这个模式把开放-封闭原则,依赖倒转原则发挥到极致了。”

“木啦木啦,木那么夸张的说,应该说就是这些设计原则的良好运用。抽象工厂模式也有缺点。你想的出来吗?”

“想不出来,我感觉它已经很好用了,哪有什么缺点?”

“是个模式就会有缺点的,都有不适用的时候,要辩证地看待问题啊。抽象工厂模式可以很方便地切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表Project,你要改动哪些地方?”

“啊,那就要至少增加三个类,Iproject、SqlServerProject、AccessProject,还需要更改IFactory、SqlServerFactory和AccessFactory才可以完全实现。啊,要改三个类,这太糟糕了的说。”

“是啊,这是非常糟糕的说。”

“还有啊,就是刚才问你的,我的客户端程序类显然不会只有一个啊,有很多地方都在使用IUser或IDepartment,而这样的设计,其实在每一个类的开始都需要声明IFactory factory = new SqlServerFactory(),如果我有100个调用数据库访问的类,是不是就要更改100次IFactory factory = new AccessFactory()这样的代码才行啊?这不能解决我要更改数据库访问时,改动一处就完全更改的要求啊!”

“改就改啊,公司花那么多钱养你干嘛啊?不就是要你努力地工作吗?100个改动,不算难的,加个班,什么都搞定了。”

“球球蛋,你讲过,编程是门艺术,这样大批量地改动,显然是非常丑陋地做法。我需要地是一个非常优雅地解决方案,我来想想办法改进一下这个抽象工厂模式。”

“好,小伙子,有立场,有想法,不向丑陋地代码低头,那就等你的好消息啦。”

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

十分钟后,小菜给出了一个改进方案。去除IFactory、SqlServerFactory和AccessFactory三个工厂类,取而代之的是一个DataAccess类,用一个简单工厂模式来实现。

代码结构图

[java]  view plain copy
  1. //DataAccess类  
  2. public class DataAccess  
  3. {  
  4.     private static final String db  = "Sqlserver";  
  5.   
  6.     public static IUser createUser()  
  7.     {  
  8.         IUser result = null;  
  9.         if ("Sqlserver".equals(db))  
  10.         {  
  11.             result = new SqlServerUser();  
  12.         }  
  13.         else if ("Access".equals(db))  
  14.         {  
  15.             result = new AccessUser();  
  16.         }  
  17.   
  18.         return result;  
  19.     }  
  20.   
  21.     public static IDepartment createDepartment()  
  22.     {  
  23.         IDepartment result = null;  
  24.         if ("Sqlserver".equals(db))  
  25.         {  
  26.             result = new SqlServerDepartment();  
  27.         }  
  28.         else if ("Access".equals(db))  
  29.         {  
  30.             result = new AccessDepartment();  
  31.         }  
  32.   
  33.         return result;  
  34.     }  
  35. }  
  36. //客户端代码  
  37. public class Main  
  38. {  
  39.     public static void main(String[] args)  
  40.     {  
  41.         User user = new User();  
  42.         Department department = new Department();  
  43.   
  44.         IUser iu = DataAccess.createUser();  
  45.   
  46.         iu.insert(user);  
  47.         iu.getUser(1);  
  48.   
  49.         IDepartment id = DataAccess.createDepartment();  
  50.   
  51.         id.insert(department);  
  52.         id.getDepartment(1);  
  53.     }  
  54. }  

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

“小菜,你确实很厉害啊,你的改进确实是比之前的代码要更进一步了,客户端已经不需要受改动数据库访问的影响了,可以打95分。为什么不能得满分,原因是如果我需要增加Oracle数据库的访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就比较麻烦了。”

“是啊,但没木办法啊,这样就需要在DataAccess类中每个方法的if分支语句里面增加了。”

15.8用反射+抽象工厂的数据访问程序

“我们要考虑的就是可不可以不在程序里写明‘如果是Sqlserver就去实例化SQL Server数据库相关的类,如果是Access就去实例化Access相关的类’这样的语句,而是根据字符串db的值去某个地方找应该要实例化的类是哪一个。这样,我们的if就可以对它说再见了。”

“听不懂啊,什么叫去哪个地方找应该要实例化的类是哪一个?”

“我要说的就是一种编程方式:依赖注入(Dependency Injection),从字面上不太好理解,我们也不去管它。关键在于如何去用这种方法来解决我们的if判断问题。本来依赖注入是需要专门的Ioc容器提供,比如Spring.NET,显然当前这个程序不需要这么麻烦,你只需要了解一个简单的‘反射’技术就可以了。”

“大鸟,你一下子说出又是依赖注入又是反射这些莫名其妙的概念,头晕。我就想知道,如何向if或者switch说byebye,至于那些什么概念我都不想了解。”

“心急讨不到好媳妇!急个毛啊?反射技术看起来很玄乎,其实实际用起来不算难。它的格式是Class.forName(className).newInstance();,只要在程序顶端写上import sun.reflect.Reflection;,就可以来引用Reflection,就可以使用反射来帮我们克服抽象工厂模式的先天不足了。”

“具体怎么做呢?”

“有了反射,我们获得实例可以用下面的两种方法。”

[java]  view plain copy
  1. //常规的写法  
  2. Iuser result = new SqlServerUser();  
  3. //反射写法  
  4. public class DataAccess  
  5. {  
  6.     private static final String db          = "SqlServer";  
  7.     private static String       className   = null;  
  8.   
  9.     public static IUser createUser()  
  10.     {  
  11.         className = db + "User";  
  12.         try  
  13.         {  
  14.             return (IUser) Class.forName(className).newInstance();  
  15.         }  
  16.         catch (ClassNotFoundException e)  
  17.         {  
  18.             e.printStackTrace();  
  19.         }  
  20.         catch (InstantiationException e)  
  21.         {  
  22.             e.printStackTrace();  
  23.         }  
  24.         catch (IllegalAccessException e)  
  25.         {  
  26.             e.printStackTrace();  
  27.         }  
  28.         return null;  
  29.     }  
  30.   
  31.     public static IDepartment createDepartment()  
  32.     {  
  33.         className = db + "Department";  
  34.         try  
  35.         {  
  36.             return (IDepartment) Class.forName(className).newInstance();  
  37.         }  
  38.         catch (InstantiationException e)  
  39.         {  
  40.             e.printStackTrace();  
  41.         }  
  42.         catch (IllegalAccessException e)  
  43.         {  
  44.             e.printStackTrace();  
  45.         }  
  46.         catch (ClassNotFoundException e)  
  47.         {  
  48.             e.printStackTrace();  
  49.         }  
  50.         return null;  
  51.     }  
  52. }  

“实例化的效果是一样的,但这两种方法的区别在哪里?”

“常规方法是写明了要实例化SqlServerUser对象。反射的写法,其实也是指明了要实例化SqlServerUser对象。”

“常规方法你可以灵活更改为AccessUser吗?”

“不可以,这都是事先编译好的代码。”

“那你看看,在反射中可以灵活更换SqlServerUser为AccessUser吗?”

“还不是一样的,写死在代码里面,等等,啊,我明白了。happy啊,我终于看明白这个东东的意思了,因为是字符串处理,可以用变量来代替,就可以根据需要更换了。Happy~”

“你丫才看到,太让我失望了,就像我对你讲四大发明之活字印刷一样的,你现在体会到面向对象带来的好处了吧。”

“嗯,我一下子知道这里面的差别所在了,主要在原来的实例化是写死在程序里面的,但现在用了反射,就可以利用字符串来实例化对象了,而且变量是可以更换的。”

“写死在程序里,太难听了,有点专业精神好不,这叫硬编码。准确地说,是将程序由编译时置为运行时。由于反射中的字符串是可以写成变量的,而变量的值到底是SQL Server,还是Access完全可以由事件的那个db变量来决定。所以就去除了switch或if判断带来的麻烦。”

代码结构图,其中DataAccess类,用反射技术,取代IFactory、SqlServerFactory和AccessFactory。

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

“这样的结果就是DataAccess.createUser()本来得到的是SqlServerUser的实例,而现在变成了OracleUser的实例了。”

“那么如果我们需要增加Project产品时,如何做呢?”

“只需要增加三个与Project相关的类,再修改DataAccess,在其中增加一个public static IProject createProject()方法就可以了。”

“怎样,编程的艺术感体现出现木?”

“哈,比以前,代码漂亮多了。但总体感觉还是有缺憾,因为在更改数据库访问时,还是需要去改程序啊,改db这个字符串的值重编译,如果可以不改程序,那才是真正地符合开放-封闭原则。而且createUser()和createDepartment()的内部实现代码几乎是完全一致的嘛。”

15.9用反射+配置文件实现数据访问程序

“小菜很追求完美嘛!我们可以复用配置文件来解决更改DataAccess的问题。”

“对啊,我可以读取文件来给DB字符串赋值,在配置文件中写明是SqlServer还是Access,这样就连DataAccess类也不用更改了。”

添加一个app.properties文件。内容如下:

    DB = SqlServer

[java]  view plain copy
  1. //再解析app.properties来获取DB字段值。  
  2. public class DataAccess  
  3. {  
  4.     private static String       DB          = null;  
  5.     private static String       className   = null;  
  6.     private static Properties   properties  = new Properties();  
  7.   
  8.     static  
  9.     {  
  10.         try  
  11.         {  
  12.             properties.load(DataAccess.class.getClassLoader()  
  13.                     .getResourceAsStream("config/app.properties"));  
  14.         }  
  15.         catch (IOException e)  
  16.         {  
  17.             e.printStackTrace();  
  18.         }  
  19.   
  20.         DB = properties.getProperty("DB");  
  21.     }  
  22.   
  23.     public static IUser createUser()  
  24.     {  
  25.         return (IUser) create("User");  
  26.     }  
  27.   
  28.     public static IDepartment createDepartment()  
  29.     {  
  30.         return (IDepartment) create("Department");  
  31.     }  
  32.   
  33.     public static Object create(String name)  
  34.     {  
  35.         className = DB + name;  
  36.   
  37.         try  
  38.         {  
  39.             return Class.forName(className).newInstance();  
  40.         }  
  41.         catch (InstantiationException e)  
  42.         {  
  43.             e.printStackTrace();  
  44.         }  
  45.         catch (IllegalAccessException e)  
  46.         {  
  47.             e.printStackTrace();  
  48.         }  
  49.         catch (ClassNotFoundException e)  
  50.         {  
  51.             e.printStackTrace();  
  52.         }  
  53.   
  54.         return null;  
  55.     }  
  56. }  

“哈哈,这下基本上可以得个满分木有啥问题了。现在我们应用了反射+抽象工厂模式解决了数据库访问时的可维护、可扩展问题。”

“从这个角度上说,所有的用简单工厂的地方,都可以考虑用反射技术消除if或switch,解除分支判断带来的耦合。”

“说的好,switch或if是程序里面的好东东,但在应对变化上,却显得老态龙钟。反射技术确实可以很好地解决它们难以应对的变化,难以维护和扩展的诟病。”

15.10无痴迷,不成功

“设计模式真的很神奇哦,如果早先是这样设计的话,我今天就用不着加班加点了。”

“好了,都快l点了,你还要不要睡觉呢?”

“啊,今天都加了一晚上的班,但学起设计模式来,我把时间都给忘记了,什么劳累都没了。”

“这就说明你是做程序员的料,一个程序员如果从来没有熬夜写程序的经历,不能算是一个好程序员,因为他没有痴迷过,所以他不会有大成就,”

“是的,无痴迷,不成功。我一定会成为优秀的程序员。我坚信。”小菜非常自信地说道。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,它通过一个工厂类来创建不同类型的对象,而无需暴露对象创建的逻辑给客户端。在Python中,简单工厂模式可以通过一个工厂类来创建不同的产品对象。下面是一个简单的示例: ```python class Product: def operation(self): pass class ConcreteProductA(Product): def operation(self): print("Performing operation A.") class ConcreteProductB(Product): def operation(self): print("Performing operation B.") class SimpleFactory: @staticmethod def create_product(product_type): if product_type == "A": return ConcreteProductA() elif product_type == "B": return ConcreteProductB() else: raise ValueError("Invalid product type.") # 使用简单工厂创建产品对象 factory = SimpleFactory() product_a = factory.create_product("A") product_a.operation() product_b = factory.create_product("B") product_b.operation() ``` 在上述示例中,`Product` 是一个抽象产品类,`ConcreteProductA` 和 `ConcreteProductB` 是具体产品类。`SimpleFactory` 是工厂类,通过 `create_product` 方法根据不同的产品类型创建相应的产品对象。 通过简单工厂模式,客户端无需知道具体的产品类,只需要通过工厂类来创建产品对象。这样可以降低客户端与具体产品类的耦合度,并且当需要新增产品时,只需要修改工厂类即可。 希望这个简单的示例能帮助你理解简单工厂模式在Python中的应用。如果有任何进一步的问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值