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


    举个栗子

    问题描述

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

    简单实现

    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表一条记录
    

    抽象工厂模式

    定义

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

    UML图

    代码实现

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

    优缺点

    优点

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

    缺点

    如果还要添加对项目表(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 分支了。

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

    配置文件(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 的话)即可达到目的,客户端也不需要任何修改。

    反射的好处

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

    总结

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

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

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

  • 相关阅读:
    2020/12/27
    2020/12/25 the light
    2020/12/25
    2020/12/23
    2020/12/22
    美加大量银行用户遭攻击,这些黑客到底牛到什么程度?
    揭秘马云的蚂蚁科技“整改”计划
    大众集团成功研发:可移动的电动汽车充电器,网友:太方便了
    东方联盟郭盛华的江湖往事
    5种方法!疫情期间防御恶意软件,网络钓鱼和诈骗
  • 原文地址:https://www.cnblogs.com/call-me-devil/p/11259982.html
Copyright © 2020-2023  润新知