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

news2024/11/29 0:46:53

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,你需要改动哪些地方?"
        "啊,那就至少要增加三个类,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.coloradmin.cn/o/1576708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

婴儿洗衣机买几公斤的合适?四大黑马婴儿洗衣机强势来袭

选择适合婴儿使用的洗衣机,是每个家长都关心的问题。在选择的时候,我们需要考虑一些因素,比如安全性、易操作性和洗涤效果等。同时,也要根据婴儿的年龄和需求来选择合适的洗衣机。婴儿洗衣机的出现,为宝妈们带来了极大…

2023年下半年中级软件设计师上午真题及答案解析

01 02 03 04 05 06 07 08 09 10 篇幅有限,私我获取免费完整 pdf文件

如何魔改 diffusers 中的 pipelines

如何魔改 diffusers 中的 pipelines 整个 Stable Diffusion 及其 pipeline 长得就很适合 hack 的样子。不管是通过简单地调整采样过程中的一些参数,还是直接魔改 pipeline 内部甚至 UNet 内部的 Attention,都可以实现很多有趣的功能或采样生图结果。 本…

JVM 全景图

今天我重新复习了一下 jvm 的一些知识点。我以前觉得 jvm 的知识点很多很碎,而且记起来很困难,但是今天我重新复习了一下,对这些知识点进行了简单的梳理之后,产生了不一样的看法。虽然 jvm 的知识点很碎,但是如果你真的…

创建型模式--1.单例模式【巴基速递】

1. 巴基的订单 在海贼世界中,巴基速递是巴基依靠手下强大的越狱犯兵力,组建的集团海贼派遣公司,它的主要业务是向世界有需要的地方输送雇佣兵(其实是不干好事儿)。 自从从特拉法尔加罗和路飞同盟击败了堂吉诃德家族 &…

系统监测工具-tcpdump的使用

一个简单的tcpdump抓包过程。主要抓包观察三次握手,四次挥手的数据包 有两个程序:客户端和服务器两个程序 服务器端的ip地址使用的是回环地址127.0.0.1 端口号使用的是6000 tcpdump -i 指定用哪个网卡等,dstip地址端口指定抓取目的地址…

ctfshow web入门 文件包含 web151--web161

web151 打算用bp改文件形式(可能没操作好)我重新试了一下抓不到 文件上传不成功 改网页前端 鼠标右键&#xff08;检查&#xff09;&#xff0c;把png改为php访问&#xff0c;执行命令 我上传的马是<?php eval($_POST[a]);?> 查看 web152 上传马 把Content-Type改为…

基于大模型的态势认知智能体

源自&#xff1a;指挥控制与仿真 作者&#xff1a;孙怡峰, 廖树范, 吴疆 李福林 “人工智能技术与咨询” 发布 摘要 针对战场态势信息众多、变化趋势认知困难的问题,提出基于大模型的态势认知智能体框架和智能态势认知推演方法。从认知概念出发,结合智能体的抽象性、具…

基于单片机收音机调幅系统设计仿真源码

**单片机设计介绍&#xff0c;基于单片机收音机调幅系统设计仿真源码 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机收音机调幅系统设计的仿真源码&#xff0c;主要实现了通过单片机控制调幅收音机的核心功能。以下是…

如何关闭WordPress的自动更新功能

Wordpress为什么自动更新 WordPress自动更新是为了提供更好的安全性和稳定性。 安全性&#xff1a;WordPress是一个广泛使用的内容管理系统&#xff0c;因此成为恶意攻击的目标。WordPress的自动更新功能确保你的网站及时获得最新的安全补丁和修复程序&#xff0c;以保护你的网…

ctfshow web入门 命令运行 web39---web52

ctfshow web入门 命令执行 昨天看了一下我的博客真的很恼火&#xff0c;不好看&#xff0c;还是用md来写吧 web39 查看源代码 看到include了&#xff0c;还是包含(其实不是) 源代码意思是当c不含flag的时候把c当php文件运行 php伪协议绕过php文件执行 data://text/plain 绕…

算法汇总啊

一些常用算法汇总 算法思想-----数据结构动态规划(DP)0.题目特点1.【重点】经典例题(简单一维dp&#xff09;1.斐波那契数列2.矩形覆盖3.跳台阶4.变态跳台阶 2.我的日常练习汇总(DP)1.蓝桥真题-----路径 算法思想-----数据结构 数据结构的存储方式 : 顺序存储(数组) , 链式存储…

ubuntu安装nginx以及开启文件服务器

1. 下载源码 下载页面&#xff1a;https://nginx.org/en/download.html 下载地址&#xff1a;https://nginx.org/download/nginx-1.24.0.tar.gz curl -O https://nginx.org/download/nginx-1.24.0.tar.gz2. 依赖配置 sudo apt install gcc make libpcre3-dev zlib1g-dev ope…

轨迹规划 | 图解最优控制LQR算法(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 最优控制理论2 线性二次型问题3 LQR的价值迭代推导4 基于差速模型的LQR控制5 仿真实现5.1 ROS C实现5.2 Python实现5.3 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全…

护眼台灯什么品牌好?揭秘护眼台灯十大排名

台灯作为我们日常生活中使用率较高的照明工具&#xff0c;光源的品质也是很重要的&#xff01;如果长时间使用一款质量不好的台灯&#xff0c;可能会影响我们的眼睛健康&#xff0c;特别是孩子的眼睛&#xff0c;还没有发育完全&#xff0c;影响更大。 要知道市面上很多劣质台…

IT廉连看——SpringBoot——SpringBoot快速入门

IT廉连看——SpringBoot——SpringBoot快速入门 1、idea创建工程 &#xff08;1&#xff09;普通Maven工程创建 工程名spring-boot-test 2、添加依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/PO…

理解 Golang 变量在内存分配中的规则

为什么有些变量在堆中分配、有些却在栈中分配&#xff1f; 我们先看来栈和堆的特点&#xff1a; 简单总结就是&#xff1a; 栈&#xff1a;函数局部变量&#xff0c;小数据 堆&#xff1a;大的局部变量&#xff0c;函数内部产生逃逸的变量&#xff0c;动态分配的数据&#x…

人工智能的分类有哪些

人工智能&#xff08;AI&#xff09;可以根据不同的分类标准进行分类。以下是一些常见的分类方法&#xff1a; 1. **按功能分类**&#xff1a; - 弱人工智能&#xff08;Narrow AI&#xff09;&#xff1a;也称为狭义人工智能&#xff0c;指专注于执行特定任务的AI系统&…

【蓝桥杯嵌入式】第十三届省赛(第二场)

目录 0 前言 1 展示 1.1 源码 1.2 演示视频 1.3 题目展示 2 CubeMX配置(第十三届省赛第二场真题) 2.1 设置下载线 2.2 HSE时钟设置 2.3 时钟树配置 2.4 生成代码设置 2.5 USART1 2.5.1 基本配置 2.5.2 NVIC 2.5.3 DMA 2.6 TIM 2.6.1 TIM2 2.6.2 TIM4 2.6.3 …

【Linux】 OpenSSH_9.3p1 升级到 OpenSSH_9.6p1(亲测无问题,建议收藏)

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…