设计模式-第12章(抽象工厂模式)

抽象工厂模式

抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
在这里插入图片描述
客户端通过不同的工厂来创建不同的产品。抽象工厂接口包含了所有产品创建的抽象方法。在创建具体产品的时候,使用具体的工厂。

AbstractFactory:抽象工厂,包含所有产品创建的抽象方法。
ConcreteFactory1和ConcreteFactory2:具体的工厂。
AbstractProductA和AbstractProductB:两个抽象产品。
ProductA1,ProductA2 和 ProductB1,ProductB2 就是对两个抽象产品的具体分类的实现。

示例:两套不同的数据库
在项目中使用两套不同的数据库,在客户端中使用抽象工厂模式,屏蔽数据库的差异。
在这里插入图片描述
使用抽象工厂模式,在抽象工厂接口中定义创建所有产品的抽象方法接口。在每个具体的工厂中实现,创建不同的产品。在客户端中使用抽象工厂接口和抽象产品,屏蔽具体的细节,面向接口,而不是面向实现。

工厂接口

public interface IFactory {
    public IUser createUser();
    public IDepartment createDepartment();
}
// Sqlserver 工厂
public class SqlserverFactory implements IFactory {
    public IUser createUser(){
        return new SqlserverUser();
    }
    public IDepartment createDepartment(){
        return new SqlserverDepartment();
    }   
}
// Access工厂
public class AccessFactory implements IFactory {
    public IUser createUser(){
        return new AccessUser();
    }
    public IDepartment createDepartment(){
        return new AccessDepartment();
    }
}

用户类接口,定义了所有的抽象方法

public interface IUser {
    public void insert(User user);
    public User getUser(int id);
}

Sqlserver 数据库实现

public class SqlserverUser implements IUser {
    //新增一个用户
    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;  
    }
}

Access 数据库实现

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;  
    }
}

部门类接口

public interface IDepartment {
    public void insert(Department department);
    public Department getDepartment(int id);
}

Sqlserver 数据库实现

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;  
    }
}

Access 数据库实现

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;  
    }
}

客户端

public class Test {
	public static void main(String[] args){
		System.out.println("**********************************************");		
		System.out.println("《大话设计模式》代码样例");
		System.out.println();	
		// 实体类user,实体类department
        User user = new User();
        Department department = new Department();
        // 如果使用的是 sqlserver数据库,就用对应的工厂,创建出操作user和department的对象
        IFactory factory = new SqlserverFactory();
		//IFactory factory = new AccessFactory(); 使用是Access数据库
		// 使用 IUser接口,屏蔽了具体的对象
        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("**********************************************");
	}
}

在上面代码中,有User类和操作User类的IUser接口,有Department类和操作Department类的IDepartment接口,每个接口下面又有两个大的分类,分别是 SqlServer数据库操作类 和 Access数据库的操作类。
解决这种多个产品系列的问题,就需要使用抽象工厂模式。

抽象工厂模式的优缺点

从上面的代码中可以看出,最大的好处是易于交换产品系列,如果使用的是 SqlServer 数据库,则只需要 IFactory factory = new SqlserverFactory(),如果使用的是 Access数据库,则只需要更改为对应的具体工厂,创建出对不同产品的操作对象。、
第二个好处,让具体的创建实例过程和客户端分离,客户端通过抽象接口来操纵实例,不会出现产品的具体类名。如上面对 User 类对象实现操纵的 IUser接口,客户端不必知道具体的操纵类,客户端只需认识 IUser和IDepartment。

简单工厂来改进抽象工厂

在上面代码中,如果要增加一个新的产品 Project ,就需要增加三个类,IProJect,SqlserverProject,AccessProject,还需要在IFactory,SqlserverFactory,AccessFactory中增加创建这个产品的方法。
这种改动是非常大的。
编程是门艺术,这样大批量的改动,是非常丑陋的做法。

增加 DataAccess类,来代替 IFactory,SqlserverFactory,AccessFactory三个工厂类。
DataAccess类是一个简单工厂,里面有创建所有产品的方法,在创建产品的时候根据不同的数据库创建出相应的产品。
在这里插入图片描述
DataAccess 类

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;
    }   
}

客户端

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("**********************************************");
	}
}

这时,如果想要增加相应的产品,只需要在 DataAccess 类中增加创建产品的方法。

反射实例化对象

反射获取实例化对象的方式,格式如下

Object result = Class.forName("包名.类名").getDeclaredConstructor().newInstance();

可以将包名类名作为变量,来在程序的运行时,创建需要的对象实例。

使用反射方式,重写 DataAccess 类。

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;
    }
}

通过反射的方式创建对象,避免了在程序使用大量的 switch 语句。

在上面代码中,如果想要改变数据库,需要改写这个变量,重写编译程序。这样是不太好的,违法了开放封闭原则。可以使用配置文件来指明数据库,这样如果想要改变数据库,只需要改写配置文件即可,不用重写编译代码。

private static String db = "Sqlserver";//数据库名称,可替换成Access

添加一个dp.properties文件,指明要使用的数据库。

db=Sqlserver 

接下来更改 DataAccess类。

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语句,解除分支判断带来的耦合。

使用反射修改商场收银程序

在商场收银代码中使用了简单工厂来创建对象,简单工厂中使用了switch语句,可以使用反射消除switch语句。
增加配置文件,定义策略类

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类


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);
    }    
}

这样更改销售策略,只需要修改配置文件即可,不需要修改代码。做到了开闭原则。

一个程序员如果从来没有熬夜写程序的经历,不能算作一个好程序员,因为他没有痴迷过,所以不会有大成就。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值