1.1 就不能不换DB吗?
都是换数据库惹的祸。
"我们团队前段时间用.net的C#来开发好一个项目,是给一家企业做的电子商务网站,是用SQL Server作为数据库的,应该说上线后除了开始有些小问题,基本都还可以。而后,公司接到另外一家公司类似需求的项目,但这家公司想省钱,租用了一个空间,只能用Access,不能用SQL Server,于是就要求我今天改造原来那个项目的代码。"
"C#与Java差不多,这不是重点,但换数据库远远没有我想得那么简单。"
"哈哈,你的麻烦来了。"
"是呀,那是相当的麻烦。但开始我觉得很简单呀,因为SQL Server和Access在ADO.NET上的使用是不同的,在SQL Server上用的是System.Data.SqlClient命名空间下的SqlConnection、SqlCommand、SqlParameter、SqlDataReader、SqlDataAdapter,而Access则要用System.Data.OleDb命名空间下的相应对象,我以为只要做一个全体替换就可以了,哪知道,替换后,错误百出。"
注:以上为.net框架上的术语,不了解并不影响阅读,只要知道数据库之间调用代码相差很大即可
"那是一定的,两者有不少不同的地方。你都找到了些什么问题?"
"实在是多呀。在插入数据时Access必须要insert into而SQL Server可以不用into的;SQL Server中的GetDate()在Access中没有,需要改成Now();SQL Server中有字符串函数Substring,而Access中根本不能用,我找了很久才知道,可以用Mid,这好像是VB中的函数。"
"insert into这是标准语法,你干吗不加into,这是自找的麻烦。"
"这些问题也就罢了,最气人的是程序的登录代码,老是报错,我怎么也找不到出了什么问题,搞了几个小时。最后才知道,原来Access对一些关键字,例如password是不能作为数据库的字段的,如果密码的字段名是password,SQL Server中什么问题都没有,运行正常,在Access中就是报错,而且报得让人莫名其妙。"
"'关键字'应该要用'['和']'包起来,不然当然是容易出错的。"
"就这样,今天加班到这时候才回来。"
"以后你还有的是班要加了。"
"为什么?"
"只要网站要维护,比如修改或增加一些功能,你就得改两个项目吧,至少在数据库中做改动,相应的程序代码都要改,甚至和数据库不相干的代码也要改,你既然有两个不同的版本,两倍的工作量也是必然的。"
"是呀,如果哪一天要用MySQL或者Oracle数据库,估计我要改动的地方更多了。"
"那是当然,MySQL、Oracle的SQL语法与SQL Server的差别更大。你的改动将是空前的。"
"哪有这么严重,大不了再加两天班就什么都搞定了。"
"菜鸟程序员碰到问题,只会用时间来摆平,所以即使整天加班,老板也不想给菜鸟加工资,原因就在于此。"
1.2 最基本的数据访问程序
写一段你原来的数据访问的做法给我看看。""那就用'新增用户'和'得到用户'为例吧。
用户类,假设只有ID和Name两个字段,其余省略。
package code.chapter15.abstractfactory1;
//用户类
public class User {
//用户ID
private int _id;
public int getId(){
return this._id;
}
public void setId(int value){
this._id=value;
}
//用户姓名
private String _name;
public String getName(){
return this._name;
}
public void setName(String value){
this._name=value;
}
}
SqlserverUser类——用于操作User表,假设只有"新增用户"和"得到用户"方法,其余方法以及具体的SQL语句省略。
package code.chapter15.abstractfactory1;
public class SqlserverUser {
//新增一个用户
public void insert(User user){
System.out.println("在SQL Server中给User表增加一条记录");
}
//获取一个用户信息
public User getUser(int id){
System.out.println("在SQL Server中根据用户ID得到User表一条记录");
return null;
}
}
package code.chapter15.abstractfactory1;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
User user = new User();
SqlserverUser su = new SqlserverUser();
su.insert(user); //新增一个用户
su.getUser(1); //得到用户ID为1的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
"这里之所以不能换数据库,原因就在于SqlserverUser su = new SqlserverUser()使得su这个对象被框死在SQL Server上了。你可能会说,是因为取名叫SqlserverUser,但即使没有Sqlserver名称,它本质上也是在使用SQL Server的SQL语句代码,确实存在耦合。如果这里是灵活的,专业点的说法,是多态的,那么在执行'su.insert(user);'和'su.getUser(1);'时就不用考虑是在用SQL Server还是在用Access。"
"我明白你的意思了,你是希望我用'工厂方法模式'来封装new SqlserverUser()所造成的变化?"
工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。
1.3 用了工厂方法模式的数据访问程序
代码结构图
IUser接口:用于客户端访问,解除与具体数据库访问的耦合。
package code.chapter15.abstractfactory2;
//用户类接口
public interface IUser {
public void insert(User user);
public User getUser(int id);
}
SqlserverUser类:用于访问SQL Server的User。
package code.chapter15.abstractfactory2;
//用户类接口
public interface IUser {
public void insert(User user);
public User getUser(int id);
}
AccessUser类:用于访问Access的User。
package code.chapter15.abstractfactory2;
public class AccessUser implements IUser {
//新增一个用户
public void insert(User user){
System.out.println("在Access中给User表增加一条记录");
}
//获取一个用户信息
public User getUser(int id){
System.out.println("在Access中根据用户ID得到User表一条记录");
return null;
}
}
IFactory接口:定义一个创建访问User表对象的抽象的工厂接口。
package code.chapter15.abstractfactory2;
//工厂接口
public interface IFactory {
public IUser createUser();
}
SqlServerFactory类:实现IFactory接口,实例化SqlserverUser。
package code.chapter15.abstractfactory2;
//Sqlserver工厂
public class SqlserverFactory implements IFactory {
public IUser createUser(){
return new SqlserverUser();
}
}
AccessFactory类:实现IFactory接口,实例化AccessUser。
package code.chapter15.abstractfactory2;
//Access工厂
public class AccessFactory implements IFactory {
public IUser createUser(){
return new AccessUser();
}
}
package code.chapter15.abstractfactory2;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
User user = new User();
IFactory factory = new SqlserverFactory();
IUser iu = factory.createUser();
iu.insert(user); //新增一个用户
iu.getUser(1); //得到用户ID为1的用户信息
IFactory factory2 = new AccessFactory();
IUser iu2 = factory2.createUser();
iu2.insert(user); //新增一个用户
iu2.getUser(1); //得到用户ID为1的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
现在如果要换数据库,只需要把new SqlServerFactory()改成new AccessFactory(),此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,却可以在运行时很好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。
这样写,代码里还是有指明'new SqlServerFactory()'呀,我要改的地方,依然很多。
问题没有完全解决,你的数据库里不可能只有一个User表吧,很可能有其他表,比如增加部门表(Department表),此时如何办呢?
package code.chapter15.abstractfactory3;
//部门类
public class Department {
//部门ID
private int _id;
public int getId(){
return this._id;
}
public void setId(int value){
this._id=value;
}
//部门名称
private String _name;
public String getName(){
return this._name;
}
public void setName(String value){
this._name=value;
}
}
1.4 用了抽象工厂模式的数据访问程序
IDepartment接口:用于客户端访问,解除与具体数据库访问的耦合。
package code.chapter15.abstractfactory3;
//部门类接口
public interface IDepartment {
public void insert(Department department);
public Department getDepartment(int id);
}
SqlserverDepartment类:用于访问SQL Server的Department。
package code.chapter15.abstractfactory3;
public class SqlserverDepartment implements IDepartment {
//新增一个部门
public void insert(Department department){
System.out.println("在SQL Server中给Department表增加一条记录");
}
//获取一个部门信息
public Department getDepartment(int id){
System.out.println("在SQL Server中根据部门ID得到Department表一条记录");
return null;
}
}
AccessDepartment类:用于访问Access的Department。
package code.chapter15.abstractfactory3;
public class AccessDepartment implements IDepartment {
//新增一个部门
public void insert(Department department){
System.out.println("在Access中给Department表增加一条记录");
}
//获取一个部门信息
public Department getDepartment(int id){
System.out.println("在Access中根据部门ID得到Department表一条记录");
return null;
}
}
IFactory接口:定义一个创建访问Department表对象的抽象的工厂接口。
package code.chapter15.abstractfactory3;
//工厂接口
public interface IFactory {
public IUser createUser();
public IDepartment createDepartment();
}
SqlServerFactory类:实现IFactory接口,并实例化SqlserverUser和SqlserverDepartment。
package code.chapter15.abstractfactory3;
//Sqlserver工厂
public class SqlserverFactory implements IFactory {
public IUser createUser(){
return new SqlserverUser();
}
public IDepartment createDepartment(){
return new SqlserverDepartment();
}
}
AccessFactory类:实现IFactory接口,实例化AccessUser和AccessDepartment。
package code.chapter15.abstractfactory3;
//Access工厂
public class AccessFactory implements IFactory {
public IUser createUser(){
return new AccessUser();
}
public IDepartment createDepartment(){
return new AccessDepartment();
}
}
package code.chapter15.abstractfactory3;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
User user = new User();
Department department = new Department();
IFactory factory = new SqlserverFactory();
//IFactory factory = new AccessFactory();
IUser iu = factory.createUser();
iu.insert(user); //新增一个用户
iu.getUser(1); //得到用户ID为1的用户信息
IDepartment idept = factory.createDepartment();
idept.insert(department); //新增一个部门
idept.getDepartment(2); //得到部门ID为2的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
这样就可以做到,只需更改IFactory factory = new SqlServerFactory()为IFactory factory = new AccessFactory(),就实现了数据库访问的切换了。
很好,实际上,在不知不觉间,你已经通过需求的不断演化,重构出了一个非常重要的设计模式。刚才不就是工厂方法模式吗?只有一个User类和User操作类的时候,是只需要工厂方法模式的,但现在显然你数据库中有很多的表,而SQL Server与Access又是两大不同的分类,所以解决这种涉及多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式。
1.5 抽象工厂模式
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。[DP]
抽象工厂模式(Abstract Factory)结构图
"AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,比如ProductA1可以理解为是SqlserverUser,而ProductB1是SqlserverDepartment。"
"这么说,IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体的工厂了。就像SqlserverFactory和AccessFactory一样。"
"理解得非常正确。通常是在运行时刻再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。"
1.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个改动,不算难的,加个班,什么都搞定了。"
"不可能,你讲过,编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。一定有更好的办法。"我来想想办法改进一下这个抽象工厂。"
"好,小伙子,有立场,有想法,不向丑陋代码低头,那就等你的好消息。"
1.7 用简单工厂来改进抽象工厂
去除IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,用一个简单工厂模式来实现。
代码结构图
package code.chapter15.abstractfactory4;
public class DataAccess {
private static String db = "Sqlserver";//数据库名称,可替换成Access
//private static 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;
}
}
package code.chapter15.abstractfactory4;
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
User user = new User();
Department department = new Department();
//直接得到实际的数据库访问实例,而不存在任何依赖
IUser iu = DataAccess.createUser();
iu.insert(user); //新增一个用户
iu.getUser(1); //得到用户ID为1的用户信息
//直接得到实际的数据库访问实例,而不存在任何依赖
IDepartment idept = DataAccess.createDepartment();
idept.insert(department); //新增一个部门
idept.getDepartment(2); //得到部门ID为2的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
"我觉得这里与其用那么多工厂类,不如直接用一个简单工厂来实现,我抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,由于事先设置了db的值(Sqlserver或Access),所以简单工厂的方法都不需要输入参数,这样在客户端就只需要DataAccess.createUser()和DataAccess.createDepartment()来生成具体的数据库访问类实例,客户端没有出现任何一个SQL Server或Access的字样,达到了解耦的目的。"
"你的改进确实是比之前的代码要更进一步了,客户端已经不再受改动数据库访问的影响了。可以打95分。"为什么不能得满分?原因是如果我需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就比较麻烦了。"
"是的,没办法,这样就需要在DataAccess类中每个方法的switch中加case了。"
1.8 用反射+抽象工厂的数据访问程序
"我们要考虑的就是可不可以不在程序里写明'如果是Sqlserver就去实例化SQL Server数据库相关类,如果是Access就去实例化Access相关类'这样的语句,而是根据字符串db的值去某个地方找应该要实例化的类是哪一个。这样,我们的switch就可以对它说再见了。"
"听不太懂哦,什么叫'去某个地方找应该要实例化的类是哪一个'?
"我要说的就是一种编程方式:依赖注入(Dependency Injection),从字面上不太好理解,我们也不去管它。关键在于如何去用这种方法来解决我们的switch问题。本来依赖注入是需要专门的IoC容器提供,比如Spring,显然当前这个程序不需要这么麻烦,你只需要再了解一个简单的Java技术'反射'就可以了。"
"你一下子说出又是'依赖注入'又是'反射'这些莫名其妙的名词,很晕。"我就想知道,如何向switch说bye-bye!至于那些什么概念我不想了解。"
"心急讨不了好媳妇!你急什么?"反射技术看起来很玄乎,其实实际用起来不算难。它的格式是:
Object result = Class.forName(className).getDeclaredConstructor().newInstance();
这样使用反射来帮我们克服抽象工厂模式的先天不足。"
"具体怎么做呢?快说快说。
"有了反射,我们获得实例可以用下面两种写法。"
//常规的写法
IUser result = new SqlserverUser();
//反射的写法
IUser result = (IUser)Class.forName("code.chapter15.abstractfactory5.SqlserverUser")
.getDeclaredConstructor().newInstance();
"实例化的效果是一样的,但这两种方法的区别在哪里?"
"常规方法是写明了要实例化SqlserverUser对象。反射的写法,其实也是指明了要实例化SqlserverUser对象呀。"
"常规方法你可以灵活更换为AccessUser吗?"
"不可以,这都是事先编译好的代码。"
"那你看看,在反射中'Class.forName("code.chapter15.abstractfactory5.SqlserverUser").getDeclaredConstructor().newInstance();',可以灵活更换'SqlserverUser'为'AccessUser'吗?"
"还不是一样,写死在代码……等等,哦!!!我明白了。""因为这里是字符串,可以用变量来处理,也就可以根据需要更换。哦,My God!太妙了!"
"哈哈,我以前对你讲四大发明之活字印刷时,曾说过'体会到面向对象带来的好处,那种感觉应该就如同是一中国酒鬼第一次喝到了茅台,西洋酒鬼第一次喝到了XO一样,怎个爽字可形容呀',你有没有这种感觉了?"
"嗯,我一下子知道这里的差别主要在原来的实例化是写死在程序里的,而现在用了反射就可以利用字符串来实例化对象,而变量是可以更换的。"
"写死在程序里,太难听了。准确地说,是将程序由编译时转为运行时。由于'Class.forName("包名。类名").getDeclaredConstructor().newInstance();'中的字符串是可以写成变量的,而变量的值到底是Sqlserver,还是Access,完全可以由事先的那个db变量来决定。所以就去除了switch判断的麻烦。"
DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory。
package code.chapter15.abstractfactory5;
import java.lang.reflect.InvocationTargetException;
public class DataAccess {
private static String assemblyName = "code.chapter15.abstractfactory5.";
private static String db ="Sqlserver";//数据库名称,可替换成Access
//创建用户对象工厂
public static IUser createUser() {
return (IUser)getInstance(assemblyName + db + "User");
}
//创建部门对象工厂
public static IDepartment createDepartment(){
return (IDepartment)getInstance(assemblyName + db + "Department");
}
private static Object getInstance(String className){
Object result = null;
try{
result = Class.forName(className).getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
"现在如果我们增加了Oracle数据访问,相关的类的增加是不可避免的,这点无论我们用任何办法都解决不了,不过这叫扩展,开放-封闭原则性告诉我们,对于扩展,我们开放。但对于修改,我们应该要尽量关闭,就目前而言,我们只需要更改private static String db ="Sqlserver";为private static String db = "Oracle";也就意味着"
"这样的结果就是DataAccess.createUser()本来得到的是SqlserverUser的实例,而现在变成了OracleUser的实例了。"
"那么如果我们需要增加Project产品时,如何做呢?"
"只需要增加三个与Project相关的类,再修改DataAccesss,在其中增加一个public static IProject createProject()方法就可以了。"
"怎么样,编程的艺术感是不是出来了?"
"哈,比以前,这代码是漂亮多了。但是,总感觉还是有点缺憾,因为在更换数据库访问时,我还是需要去改程序(改db这个字符串的值)重编译,如果可以不改程序,那才是真正地符合开放-封闭原则。"
1.9 用反射+配置文件实现数据访问程序
"我们还可以利用配置文件来解决更改DataAccess的问题。"
"哦,对的,对的,我可以读文件来给DB字符串赋值,在配置文件中写明是Sqlserver还是Access,这样就连DataAccess类也不用更改了。"
添加一个db.properties文件,内容如下。
db=Sqlserver
再更改DataAccess类,添加与读取文件内容相关的包。
package code.chapter15.abstractfactory6;
import java.lang.reflect.InvocationTargetException;
//与读文件内容相关的包
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class DataAccess {
private static String assemblyName = "code.chapter15.abstractfactory6.";
public static String getDb() {
String result="";
try{
Properties properties = new Properties();
//编译后,请将db.properties文件复制到要编译的class目录中,并确保下面path路径与
//实际db.properties文件路径一致。否则会报No such file or directory错误
String path=System.getProperty("user.dir")+"/code/chapter15/abstractfactory6/db.properties";
System.out.println("path:"+path);
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
properties.load(bufferedReader);
result = properties.getProperty("db");
}
catch(IOException e){
e.printStackTrace();
}
return result;
}
//创建用户对象工厂
public static IUser createUser() {
String db=getDb();
return (IUser)getInstance(assemblyName + db + "User");
}
//创建部门对象工厂
public static IDepartment createDepartment(){
String db=getDb();
return (IDepartment)getInstance(assemblyName + db + "Department");
}
private static Object getInstance(String className){
Object result = null;
try{
result = Class.forName(className).getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
"将来要更换数据库,根本无须重新编译任何代码,只需要更改配置文件就好了。这下基本可以算是满分了,现在我们应用了反射+抽象工厂模式解决了数据库访问时的可维护、可扩展的问题。"
"从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。"
"说得没错,switch或者if是程序里的好东西,但在应对变化上,却显得老态龙钟。反射技术的确可以很好地解决它们难以应对变化,难以维护和扩展的诟病。"
1.10 商场收银程序再再升级
"还记得我们在策略模式、装饰模式、工厂方法模式都学习过的商场收银程序吗?"
"我记得,当时做了很多次的重构升级,感觉代码的可维护、可扩展能力都提高很多很多。"
"今天我们学习了反射,你想想看,那个代码,还有重构的可能性吗?"
"呃!我想想看。原来的CashContext是有一个长长的switch,这是可以用反射来解决的。"
经过一定时间的思考,对代码改进如下:
首先要制作一个可以很容易修改的文本配置文件data.properties,将它放在编译的.class同一目录下。
strategy1=CashRebateReturnFactory,1d,0d,0d
strategy2=CashRebateReturnFactory,0.8d,0d,0d
strategy3=CashRebateReturnFactory,0.7d,0d,0d
strategy4=CashRebateReturnFactory,1d,300d,100d
strategy5=CashRebateReturnFactory,0.8d,300d,100d
strategy6=CashReturnRebateFactory,0.7d,200d,50d
修改CashContext类。
先修改构造方法,此时已经没有了长长的switch,直接读文件配置即可。
增加两个函数,一个用来读配置文件,一个通过反射生成实例。
package code.chapter15.abstractfactory7;
import java.lang.reflect.InvocationTargetException;
//与读文件内容相关的包
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class CashContext {
private static String assemblyName = "code.chapter15.abstractfactory7.";
private ISale cs; //声明一个ISale接口对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
String[] config = getConfig(cashType).split(",");
IFactory fs=getInstance(config[0],
Double.parseDouble(config[1]),
Double.parseDouble(config[2]),
Double.parseDouble(config[3]));
this.cs = fs.createSalesModel();
}
//通过文件得到销售策略的配置文件
private String getConfig(int number) {
String result="";
try{
Properties properties = new Properties();
String path=System.getProperty("user.dir")+"/code/chapter15/abstractfactory7/data.properties";
System.out.println("path:"+path);
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
properties.load(bufferedReader);
result = properties.getProperty("strategy"+number);
}
catch(IOException e){
e.printStackTrace();
}
return result;
}
//根据配置文件获得相关的对象实例
private IFactory getInstance(String className,double a,double b,double c){
IFactory result = null;
try{
result = (IFactory)Class.forName(assemblyName+className)
.getDeclaredConstructor(new Class[]{double.class,double.class,double.class})
.newInstance(new Object[]{a,b,c});
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
"此时,我们如果需要更改销售策略,不再需要去修改代码了,只需要去改data.properties文件即可。我们的每个代码都尽量做到了'向修改关闭,向扩展开放'。"
1.11 无痴迷,不成功
"设计模式真的很神奇哦,如果早先这样设计,我今天就用不着加班加点了。""这就说明你是做程序员的料,一个程序员如果从来没有熬夜写程序的经历,不能算是一个好程序员,因为他没有痴迷过,所以他不会有大成就。"
"是的,无痴迷,不成功。我一定会成为优秀的程序员。我坚信.