能不能换DB吗?--抽象工厂模式

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 {//用户IDprivate 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 {//部门IDprivate 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,你需要改动哪些地方?"
        "啊,那就至少要增加三个类,IProjectSqlserverProjectAccessProject,还需要更改IFactorySqlserverFactoryAccessFactory才可以完全实现。啊,要改三个类,这太糟糕了。"
        "是的,这非常糟糕。"
        "还有就是刚才问你的,我的客户端程序类显然不会是只有一个,有很多地方都在使用IUserIDepartment,而这样的设计,其实在每一个类的开始都需要声明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 无痴迷,不成功

        "设计模式真的很神奇哦,如果早先这样设计,我今天就用不着加班加点了。""这就说明你是做程序员的料,一个程序员如果从来没有熬夜写程序的经历,不能算是一个好程序员,因为他没有痴迷过,所以他不会有大成就。"
"是的,无痴迷,不成功。我一定会成为优秀的程序员。我坚信.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/301187.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【2024】Rancher的安装与介绍

———————————————————————————— 记录一下rancher的学习与使用过程 本部分内容包括rancher的介绍、特点、与k8s关系和部署等内容 ———————————————————————————— Rancher是什么? 简单来说,Ranc…

ChatGPT 在做什么,为什么有效?

原文:What Is ChatGPT Doing … and Why Does It Work? 译者:飞龙 协议:CC BY-NC-SA 4.0 序言 这本简短的书试图从第一原理解释 ChatGPT 是如何工作的。在某种程度上,这是关于技术的故事。但它也是关于科学的故事。以及关于哲学…

MySQL的内连接和外连接

内连接 在之前的MySQL的复合查询时,我们能够通过给两个表做笛卡尔积查询和where子句限定条件来查询想查询的数据,不过MySQL还提供了内连接用来给两个表做笛卡尔积,对比之前的复合查询笛卡尔积来说语法更加简洁。 语法:select 字段…

基于51单片机的温度、烟雾、火焰检测设计

基于51单片机的火灾检测设计 (仿真+程序+原理图设计报告) 功能介绍 具体功能: 1.使用MQ-2烟雾采集,使用ADC0832将传感器输出的模拟信号转化为数字信号,再传给单片机。 2.使用DS18B20采集温度。…

Linux中shell脚本的学习第一天,编写脚本的规范,脚本注释、变量,特殊变量的使用等,包含面试题

4月7日没参加体侧的我自学shell的第一天 Shebang 计算机程序中,shebang指的是出现在文本文件的第一行前两个字符 #! 1)以#!/bin/sh 开头的文件,程序在执行的时候会调用/bin/sh, 也就是bash解释器 2)以#!/usr/bin/python 开头的文件&#…

动态代理

动态代理 动态代理和静态代理角色一致。 代理类是动态生成的,不是我们直接写好的。 动态代理分为俩大类:基于接口的动态代理、基于类的动态代理 基于接口:JDK动态代理(以下示例就是这个) 基于类:cglib java字节码实现:javasist JDK动态代理 InvocationHandler Proxy …

记一次Cannot deploy POJO class [xxx$$EnhancerBySpringCGLIB$$xxx]的错误

最近项目上需要使用websocket做服务端&#xff0c;那好说啊&#xff0c;直接springboot集成的websocket 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><versi…

Qt 实现简易的视频播放器,功能选择视频,播放,暂停,前进,后退,进度条拖拉,视频时长显示

1.效果图 2.代码实现 2.1 .pro文件 QT core gui multimedia multimediawidgets 2.2 .h文件 #ifndef VIDEOPLAYING_H #define VIDEOPLAYING_H#include <QWidget> #include<QFileDialog>#include<QMediaPlayer> #include<QMediaRecorder> #in…

第十四届蓝桥杯C/C++大学B组题解(一)

1、日期统计 #include <bits/stdc.h> using namespace std; int main() {int array[100] {5, 6, 8, 6, 9, 1, 6, 1, 2, 4, 9, 1, 9, 8, 2, 3, 6, 4, 7, 7,5, 9, 5, 0, 3, 8, 7, 5, 8, 1, 5, 8, 6, 1, 8, 3, 0, 3, 7, 9,2, 7, 0, 5, 8, 8, 5, 7, 0, 9, 9, 1, 9, 4, 4, 6,…

manga-ocr漫画日文ocr

github 下载 解压 anaconda新建环境 conda create -n manga_ocr python3.8 激活环境 conda activate manga_ocr cd到解压目录 cd /d manga-ocr-master 安装依赖包 pip install -r requirements.txt pip3 install manga-ocr 下载离线model huggingface 123云盘 解压到一个目录…

实践笔记-03 docker buildx 使用

docker buildx 使用 1.启用docker buildx2.启用 binfmt_misc3.从默认的构建器切换到多平台构建器3.1创建buildkitd.toml文件&#xff08;私有仓库是http没有证书的情况下&#xff0c;需要配置&#xff09;3.2创建构建器并使用新创建的构建器 4.构建多架构镜像并推送至harbor仓库…

在虚拟机尝试一次用启动盘重装系统

在虚拟机尝试一次用启动盘重装系统 没有自己重装过系统&#xff0c;也不敢对自己的笔记本下手&#xff0c;用虚拟机重装玩玩试试。 先设置成u盘启动 从boot中选择相应的创建的硬盘即可&#xff08;刚刚突然发现图片不能上传了&#xff0c;经过乱七八糟的尝试后&#xff0c;开一…

管廊ar实景可视化巡检提升安全性

在科技日新月异的今天&#xff0c;智慧工地ar远程巡检交互系统应运而生&#xff0c;它是ar开发公司深圳华锐视点综合运用了AR增强现实、5G通信、人工智能、物联网以及GPS北斗定位等前沿技术&#xff0c;为企业打造了一套全新的数字化巡检解决方案。不仅解放了巡检人员的双手&am…

学习周报:文献阅读+Fluent案例+水力学理论学习

目录 摘要 Abstract 文献阅读&#xff1a;物理信息的神经网络与湍流传质的非封闭机制模型相结合 文献摘要 提出问题 提出方案 实验设置 所需方程介绍 雷诺时均方程&#xff08;RANS&#xff09; K-epsilon两方程模型 神经网络框架 DNN部分 损失函数定义 PINN部分…

笔记 | 编译原理L1

重点关注过程式程序设计语言编译程序的构造原理和技术 1 程序设计语言 1.1 依据不同范型 过程式(Procedural programming languages–imperative)函数式(Functional programming languages–declarative)逻辑式(Logical programming languages–declarative)对象式(Object-or…

C语言——顺序表

文章目录 一、线性表二、顺序表顺序表和数组的区别顺序表的分类1.静态顺序表2.动态顺序表 三、动态顺序表的实现1.动态顺序表头文件2.动态顺序表源文件3.测试源文件 一、线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。线性表是⼀种…

在Excel中把打印内容打印在一页上的5种方法,总有一种适合你

序言 如果你经常通过在Microsoft Excel中打印不必要的单元格区域而用完打印机纸张,那么可以按照本Excel教程中提到的经过尝试和测试的方法,学习如何在一页上打印Excel工作表。 由于Excel工作表不像Microsoft Word那样显示单页布局,因此在打印这些电子表格时,你经常会感到…

HBuilder开发者必备!Windows上传IPA文件的软件分享

摘要&#xff1a;HBuilder是目前市场上最受欢迎的移动应用开发框架&#xff0c;但是我们很多人在使用的时候&#xff0c;由于没有mac电脑&#xff0c;无法使用xcode或者application loader上传ipa文件到App Store。本篇博客介绍了一种通过网页平台上传ipa文件的方法&#xff0c…

蓝桥杯 经验技巧篇

1. 注意事项 &#x1f468;‍&#x1f3eb; 官方通知 &#x1f468;‍&#x1f3eb; 资料文档 时间&#xff1a;4月13日 9:00~13:00 &#xff08;时长 4小时&#xff09;物品 准考证&#xff08;赛前一周开放下载&#xff0c;自行打印&#xff09;学生证身份证笔、水、外套&a…

Java学习之原子性操作(Atomic)

CAS&#xff08;compare and swap&#xff09;&#xff1a; public static void main(String[] args) throws InterruptedException {// AtomicInteger是一个提供原子操作的Integer类&#xff0c;通过CAS思想实现AtomicInteger a new AtomicInteger(0);System.out.println(a.…