3、大话设计模式之抽象工厂模式详解

1、最基本的数据访问程序

用‘新增用户’和‘得到用户为例’

用户类,假设只有ID和Name两个字段

/**
 * 用户类
 * Created by ZhuPengWei on 2018/1/26.
 */
public class User {

    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SqlServerUser类:用户操作User表,假设只有新增用户和得到用户的方法,其余的方法以及具体的SQL语句省略。

/**
 * 操作user表
 * Created by ZhuPengWei on 2018/1/26.
 */
public class SqlServerUser {
    public void insert(User user) {
        System.out.println("在SQLServer中给User表增加一条记录");
    }

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

客户端代码

/**
 * 客户端
 * Created by ZhuPengWei on 2018/1/26.
 */
public class Client {
    public static void main(String[] args) {
        User user = new User();
        // 与SQLServer耦合
        SqlServerUser sqlServerUser = new SqlServerUser();
        // 插入用户
        sqlServerUser.insert(user);
        // 得到id为1的用户
        User u = sqlServerUser.getUser(1);
    }
}

新增需求:如果要使得用户不使用SQL SERVER数据库而使用MySQL
上述的代码是不满足需求的,原因就在于 SqlServerUser sqlServerUser = new SqlServerUser()使得sqlServerUser这个对象被框死在SQL Server上了。如果这里是
灵活的,专业点的说法是多态的,那么在执行sqlServerUser.insert(user)和sqlServerUser.getUser(1)时就不用考虑是在用SQL Server还是用MySQL。
所以,可以使用工厂方法模式来封装new SqlServer()所造成的变化
工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类

2、用工厂方法模式的数据访问程序

代码结构图
这里写图片描述

IUser接口:用于客户端访问,解除与具体数据库访问的耦合。

/**
 * 用于客户端访问,解除与具体数据库访问的耦合
 * Created by ZhuPengWei on 2018/1/26.
 */
public interface IUser {

    void insert(User user);

    User getUser(int id);
}

SqlServerUser类:用于访问SqlServer的User。

/**
 * 用于访问SqlServer的User
 * Created by ZhuPengWei on 2018/1/26.
 */
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;
    }
}

MySqlUser类:用于访问MySql的User。

/**
 * 用于访问MySQL的User
 * Created by ZhuPengWei on 2018/1/26.
 */
public class MySqlUser implements IUser {
    @Override
    public void insert(User user) {
        System.out.println("在MySQL中给User表增加一条记录");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在MySQL中根据Id查询User表一条记录");
        return null;
    }
}

IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。

/**
 * 定义一个创建访问User表对象的抽象的工厂接口
 * Created by ZhuPengWei on 2018/1/26.
 */
public interface IFactory {
    IUser createUser();
}

SqlServerFactory类,实现IFactory接口,实例化SqlServerUser。

/**
 * 实例化SqlServerUser。
 * Created by ZhuPengWei on 2018/1/26.
 */
public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

MySqlFactory:实现IFactory接口,实例化MySqlUser。

/**
 * 实例化MySqlUser。
 * Created by ZhuPengWei on 2018/1/26.
 */
public class MySqlFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new MySqlUser();
    }
}

客户端代码

/**
 * 客户端
 * Created by ZhuPengWei on 2018/1/26.
 */
public class Client {
    public static void main(String[] args) {
        User user = new User();
        // 若要更改成MySql数据库,只需要将本句改成 IFactory factory = new MySqlFactory();
        IFactory factory = new SqlServerFactory();
        IUser iu = factory.createUser();
        iu.insert(user);
        iu.getUser(1);
    }
}

3、用抽象工厂模式的数据访问程序

再次的修改代码,增加了关于部门表的处理
代码结构图
这里写图片描述

此时IFactory

/**
 * 定义一个创建访问User表对象的抽象的工厂接口
 * Created by ZhuPengWei on 2018/1/26.
 */
public interface IFactory {
    IUser createUser();

    IDepartment createDepartment();
}

其余略过
客户端代码

/**
 * 客户端
 * Created by ZhuPengWei on 2018/1/26.
 */
public class Client {
    public static void main(String[] args) {
        User user = new User();
        Department department = new Department();

        // IFactory factory=new SqlServerFactory();
        IFactory factory = new MySqlFactory();
        IUser iUser = factory.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        IDepartment iDepartment = factory.createDepartment();
        iDepartment.insert(department);
        iDepartment.getDepartment(1);
    }
}

抽象工厂模式的优点与缺点
优点
1、抽象工厂模式最大的好处便是交换产品系列,由于具体工厂类,例如IFactory factory = new MySqlFactory(),在一个应用中只需要在初始化的时候出现一次,
这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂就可以使用不通的产品配置
2、它让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点
抽象工厂模式可以很方便地切换两个数据库访问的代码,但是比如说要增加一张项目表(Project),那至少要增加三个类IProject、SqlServerProject、MySqlProject,还
需要更改IFactory、SqlServerFactory和MySqlFactory才可以完全实现。

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

编程是门艺术,这样大批量的改动,显然是十分丑陋的做法。

去除IFactory,SqlServerFactory和MySqlFactory三个工厂类,取而代之的是DataAccess类,用一个简单的工厂来实现

这里写图片描述

/**
 * 替代IFactory,SqlServerFactory和MySqlFactory
 * 简单工厂模式
 * Created by ZhuPengWei on 2018/1/26.
 */
public class DataAccess {
    // 数据库名称,可替换成SQLSERVER
    private static final String DB = "MYSQL";

    public static IUser createUser() {
        IUser result = null;
        switch (DB) {
            case "MYSQL":
                result = new MySqlUser();
                break;
            case "SQLSERVER":
                result = new SqlServerUser();
                break;
        }
        return result;
    }

    public static IDepartment createDepartment() {
        IDepartment result = null;
        switch (DB) {
            case "MYSQL":
                result = new MySqlDepartment();
                break;
            case "SQLSERVER":
                result = new SqlServerDepartment();
                break;
        }
        return result;
    }
}

这样的改进确实是比之前的代码更近一步了,客户端已经不再受改动数据库访问的影响,但是如果我需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,
现在就需要在DataAccess类中每个方法的switch中加case了。

使用反射+配置文件实现数据访问程序,改进简单工厂模式

/**
 * 用反射+配置文件实现数据访问程序
 * 简单工厂模式
 * Created by ZhuPengWei on 2018/1/26.
 */
public class DataAccess {


    private static final ResourceBundle rb;

    // 数据库名称,根据配置文件读取
    private static final String DB;

    static {
        // 获得资源包
        rb = ResourceBundle.getBundle("pers/zpw/AbstractFactoryPattern/config");
        DB = rb.getString("DB");
    }


    public static IUser createUser() {
        try {
            return (IUser) Class.forName("pers.zpw.AbstractFactoryPattern.version3." + DB + "User").newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public static IDepartment createDepartment() {
        try {
            return (IDepartment) Class.forName("pers.zpw.AbstractFactoryPattern.version3." + DB + "Department").newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

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

猜你喜欢

转载自blog.csdn.net/qq_36144258/article/details/79177607