大话设计模式笔记(十二)の抽象工厂模式

举个栗子

问题描述

模拟访问数据库“新增用户”和“获得用户”,用户类假设只有ID和Name两个字段。sql

简单实现

User

/**
 * 用户类
 * Created by callmeDevil on 2019/7/28.
 */
public class User {

    private int id;
    private String name;

    // 省略 get set 方法

}

SqlServerUser

/**
 * 假设sqlServer 链接,用于操做User表
 * Created by callmeDevil on 2019/7/28.
 */
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;
    }

}

测试

public class Test {

    public static void main(String[] args) {
        User user = new User();
        SqlServerUser su = new SqlServerUser();
        su.insert(user);
        su.getUser(user.getId());
    }

}

测试结果

在SQL Server中给User表增长一条记录
在SQL Server中根据ID获得User表一条记录

存在问题

若是须要链接别的数据库,那么这个写法没法扩展,下面使用工厂方法模式实现数据库

工厂方法模式实现

IUser

/**
 * 用于客户端访问,解除与具体数据库访问的耦合
 * Created by callmeDevil on 2019/7/28.
 */
public interface IUser {
    void insert(User user);
    User getUser(int id);
}

SqlServerUser

/**
 * 用于访问SQL Server 的User
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在SQL Server中给User表增长一条记录");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在SQL Server中根据ID获得User表一条记录");
        return null;
    }

}

AccessUser

/**
 * 用于访问Access 的User
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在Access 中给User表增长一条记录");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在在Access中根据ID获得User表一条记录");
        return null;
    }

}

IFactory

/**
 * 定义一个建立访问User 表对象的抽象工厂接口
 * Created by callmeDevil on 2019/7/28.
 */
public interface IFactory {
    IUser createUser();
}

SqlServerFactory

/**
 * 实现IFactory 接口,实例化SQLServerUser
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

AccessFactory

/**
 * 实现IFactory 接口,实例化AccessUser
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new AccessUser();
    }
}

测试

public class Test {
    public static void main(String[] args) {
        User user = new User();
        // 若要更改为 Access 数据库,只须要将此处改为
        // IFactory factory = new AccessFactory();
        IFactory factory = new SqlServerFactory();
        IUser iUser = factory.createUser();
        iUser.insert(user);
        iUser.getUser(1);
    }
}

测试结果同上。编程

增长需求

若是要增长一个部门表(Department),须要怎么改?设计模式

修改实现

Department

/**
 * 部门表
 * Created by callmeDevil on 2019/7/28.
 */
public class Department {

    private int id;
    private String name;

    // 省略 get set 方法

}

IDepartment

/**
 * 用于客户端访问,解除与具体数据库访问的耦合
 * Created by callmeDevil on 2019/7/28.
 */
public interface IDepartment {
    void insert(Department department);
    Department getDepartment(int id);
}

SqlServerDepartment

/**
 * 用于访问SqlServer 的Department
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerDepartment implements IDepartment {

    @Override
    public void insert(Department department) {
        System.out.println("在 SqlServer 中给Department 表增长一条记录");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在SQL Server中根据ID获得Department表一条记录");
        return null;
    }

}

AccessDepartment

/**
 * 用于访问Access 的Department
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessDepartment implements IDepartment {

    @Override
    public void insert(Department department) {
        System.out.println("在Access 中给Department 表增长一条记录");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在Access 中根据ID获得Department表一条记录");
        return null;
    }

}

IFactory

/**
 * 定义一个建立访问User 表对象的抽象工厂接口
 * Created by callmeDevil on 2019/7/28.
 */
public interface IFactory {
    IUser createUser();
    IDepartment createDepartment(); //增长的接口方法
}

SqlServerFactory

/**
 * 实现IFactory 接口,实例化SQLServerUser
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment(); //增长了SqlServerDepartment 工厂
    }

}

AccessFactory

/**
 * 实现IFactory 接口,实例化AccessUser
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new AccessUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new AccessDepartment(); //增长了AccessDepartment 工厂
    }

}

测试

public class Test {
    public static void main(String[] args) {
        User user = new User();
        Department dept = new Department();
        // 只需肯定实例化哪个数据库访问对象给 factory
        IFactory factory = new AccessFactory();
        // 则此时已于具体的数据库访问解除了依赖
        IUser iUser = factory.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        IDepartment iDept = factory.createDepartment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

测试结果

在Access 中给User表增长一条记录
在Access 中根据ID获得User表一条记录
在Access 中给Department 表增长一条记录
在Access 中根据ID获得Department表一条记录

抽象工厂模式

定义

提供一个建立一系列相关或相互依赖对象的接口,而无需指定它们具体的类。ide

UML图

代码实现

实际上上面的修改实现已经知足抽象工厂模式的实现方式,此处再也不举例。sqlserver

优缺点

优势

  • 最大的好处即是易于交换产品系列,因为不一样的具体工厂类,在一个应用中只须要在初始化到时候出现一次,这就使得改变一个应用的具体工厂变得很是容易,它只须要改变具体工厂便可使用不一样的产品配置
  • 让具体的建立实例改为与客户端分离,客户端是经过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出如今客户代码中

缺点

若是还要添加对项目表(Project)的访问,那么须要增长三个类,IProject、SQLServerProject、AccessProject,还须要更改 IFactory、ISQLServerFactory、AccessFactory 才能够彻底实现,这太糟糕了。编程是门艺术,这样大批量的改动,显然是很是丑陋的作法。测试

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

去除IFactory、SQLServerFactory、AccessFactory,改成一个 DataAccess,用一个简单工厂模式来实现。设计

结构图

代码实现

DataAccess

/**
 * 统一管理数据库访问
 * Created by callmeDevil on 2019/7/28.
 */
public class DataAccess {

    // 数据库名称,可替换成 Access
    private static final String DB = "SqlServer";
//    private static final String DB = "Access";

    public static IUser createUser() {
        IUser user = null;
        switch (DB) {
            case "SqlServer":
                user = new SqlServerUser();
                break;
            case "Access":
                user = new AccessUser();
                break;
            default:
                break;
        }
        return user;
    }

    public static IDepartment createDepartment() {
        IDepartment department = null;
        switch (DB) {
            case "SqlServer":
                department = new SqlServerDepartment();
                break;
            case "Access":
                department = new AccessDepartment();
                break;
            default:
                break;
        }
        return department;
    }

}

测试

public class Test {
    public static void main(String[] args) {
        User user = new User();
        Department dept = new Department();
        // 直接获得实际的数据库访问实例,而不存在任何的依赖
        IUser iUser = DataAccess.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        IDepartment iDept = DataAccess.createDepartment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

测试结果

在SQL Server中给User表增长一条记录
在SQL Server中根据ID获得User表一条记录
在SQL Server中给Department 表增长一条记录
在SQL Server中根据ID获得Department表一条记录

存在问题

虽然解决了抽象工厂模式中须要修改太多地方的问题,但又回到了简单工厂模式一开始的问题了,就是若是要链接 Oracle 数据库,那么须要修改的地方则是 DataAccess 类中全部方法的 swicth 中加 case 分支了。code

用配置文件+反射+抽象工厂实现

配置文件(db.properties)

# 数据库名称,可更改为 Access
db=SqlServer

DataAccess

/**
 * 统一管理数据库访问
 * Created by callmeDevil on 2019/7/28.
 */
public class DataAccess {

    // 数据库名称,从配置文件中获取
    private static String DB;

    public static IUser createUser() throws Exception {
        if (DB == null || DB.trim() == "") {
            return null;
        }
        // 拼接具体数据库访问类的权限定名
        String className = "com.xxx." + DB + "User";
        return (IUser) Class.forName(className).newInstance();
    }

    public static IDepartment createDeptment() throws Exception {
        if (DB == null || DB.trim() == "") {
            return null;
        }
        // 拼接具体数据库访问类的权限定名
        String className = "com.xxx." + DB + "Department";
        return (IDepartment) Class.forName(className).newInstance();
    }

    public static String getDB() {
        return DB;
    }

    public static void setDB(String DB) {
        DataAccess.DB = DB;
    }

}

测试

public class Test {
    public static void main(String[] args) throws Exception {
        // 加载配置文件
        Properties properties = new Properties();
        InputStream is = new FileInputStream(new File("xxx\\db.properties")); // 配置文件所在路径,当前方式采用绝对路径获取
        properties.load(is);
        is.close();
        String db = properties.getProperty("db");
        // 使用具体的数据库告诉管理类
        DataAccess dataAccess = new DataAccess();
        dataAccess.setDB(db);

        User user = new User();
        IUser iUser = dataAccess.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        Department dept = new Department();
        IDepartment iDept = dataAccess.createDeptment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

测试结果

在SQL Server中给User表增长一条记录
在SQL Server中根据ID获得User表一条记录
在SQL Server中给Department 表增长一条记录
在SQL Server中根据ID获得Department表一条记录

如今若是咱们增长了 Oracle 数据库访问,相关类的增长是不可避免的,这点不管用任何办法都解决不了,不过这叫扩展,开放-封闭原则告诉咱们,对于扩展,咱们开放,但对于修改,咱们应该尽可能关闭,就目前实现方式而言,只须要将配置文件中改成 Oracle (若是新增的具体访问类名称为 OracleUserOracleDepartment 的话)便可达到目的,客户端也不须要任何修改。server

反射的好处

全部在用简单工厂的地方,均可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合。

总结

能够发现到目前为止,就“工厂”而言,已经包含了三种设计模式:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

对上述模式的不一样点将在后续推出,本文篇幅已通过长,此处先不叙述。

相关文章
相关标签/搜索