设计模式_11:抽象工厂模式

来源:互联网 发布:网络的坏处有哪些500字 编辑:程序博客网 时间:2024/05/21 11:35

场景:假设现在有一个专门操作mysql数据库user表的dao,可以对其进行插入和查询,,以下是代码:

public class Main {    public static void main(String[] args) {        User user = new User(0, "Lisa");        MysqlUserDao mysqlUserDao = new MysqlUserDao();        mysqlUserDao.insertUser(user);        mysqlUserDao.queryUserById(0);    }}//实体类class User {    private int id;    private String name;    public User(int id, String name) {        this.id = id;        this.name = 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;    }    @Override    public String toString() {        return "User{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//操作Mysql的dao层class MysqlUserDao {    public void insertUser(User user){        System.out.println("向Mysql数据库User表中插入一条数据:" + user);    }    public User queryUserById(int id) {        System.out.println("在Mysql数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "测试");    }}

运行结果:

向Mysql数据库User表中插入一条数据:User{id=0, name='Lisa'}在Mysql数据库User表中查询一条id为0的数据

但是如果要换成使用另一种数据库(比如Access)的话,是需要改动很多代码的,因为MysqlUserDao mysqlUserDao = new MysqlUserDao()

已经写死dao的类型是MysqlUserDao,如果整个项目有100多个地方有这段代码,就意味着要改动100多次类型,不符合开闭原则,因此,而且不同数据库之间的sql函数和语法都有部分差异,所以先用工厂方法模式来改良一下:

public class Main {    public static void main(String[] args) {        User user = new User(0, "Lisa");        IUserDaoFactory mysqlUserDaoFactory = new MysqlUserDaoFactory();        //这里改成IUserDao,这样mysqlUserDao事先就不用知道访问的是哪个数据库了        IUserDao mysqlUserDao = mysqlUserDaoFactory.createUserDao();        mysqlUserDao.insertUser(user);        mysqlUserDao.queryUserById(0);        System.out.println("这里只需把new MysqlDaoFactory()改成new AccessDaoFactory()即可完成dao的切换");        IUserDaoFactory accessUserDaoFactory = new AccessUserDaoFactory();        IUserDao accessUserDao = accessUserDaoFactory.createUserDao();        accessUserDao.insertUser(user);        accessUserDao.queryUserById(0);    }}//用户实体类class User {    private int id;    private String name;    public User(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "User{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//抽象工厂接口interface IUserDaoFactory {    IUserDao createUserDao();}//具体的MysqlDao工厂class MysqlUserDaoFactory implements IUserDaoFactory {    @Override    public IUserDao createUserDao() {        return new MysqlUserDao();    }}//具体的accessDao工厂class AccessUserDaoFactory implements IUserDaoFactory {    @Override    public IUserDao createUserDao() {        return new AccessUserDao();    }}//抽象dao接口interface IUserDao {    void insertUser(User user);    User queryUserById(int id);}//操作Mysql的dao层class MysqlUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Mysql数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Mysql数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "mysql测试");    }}//操作Access的dao层class AccessUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Access数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Access数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "access测试");    }}
运行结果:

向Mysql数据库User表中插入一条数据:User{id=0, name='Lisa'}在Mysql数据库User表中查询一条id为0的数据这里只需把new MysqlDaoFactory()改成new AccessDaoFactory()即可完成dao的切换向Access数据库User表中插入一条数据:User{id=0, name='Lisa'}在Access数据库User表中查询一条id为0的数据

显然,数据库中不可能只有一个User表,比如说还有个Department部门表,我们可以加多一个对部门表操作的dao试试:

public class Main {    public static void main(String[] args) {        User user = new User(0, "Lisa");        Department department = new Department(0, "财务部");        IDaoFactory mysqlUserDaoFactory = new MysqlDaoFactory();        IUserDao mysqlUserDao = mysqlUserDaoFactory.createUserDao();        mysqlUserDao.insertUser(user);        mysqlUserDao.queryUserById(0);        IDepartmentDao mysqlDepartmentDao = mysqlUserDaoFactory.createDepartmentDao();        mysqlDepartmentDao.insertDepartment(department);        mysqlDepartmentDao.queryDepartmentById(0);        System.out.println("这里只需把new MysqlDaoFactory()改成new AccessDaoFactory()即可完成dao的切换");        IDaoFactory accessUserDaoFactory = new AccessDaoFactory();        IUserDao accessUserDao = accessUserDaoFactory.createUserDao();        accessUserDao.insertUser(user);        accessUserDao.queryUserById(0);        IDepartmentDao accessDepartmentDao = accessUserDaoFactory.createDepartmentDao();        accessDepartmentDao.insertDepartment(department);        accessDepartmentDao.queryDepartmentById(0);    }}//用户实体类class User {    private int id;    private String name;    public User(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "User{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//部门实体类class Department {    private int id;    private String name;    public Department(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "Department{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//抽象工厂接口interface IDaoFactory {    IUserDao createUserDao();    IDepartmentDao createDepartmentDao();}//具体的MysqlDao工厂class MysqlDaoFactory implements IDaoFactory {    @Override    public IUserDao createUserDao() {        return new MysqlUserDao();    }    @Override    public IDepartmentDao createDepartmentDao() {        return new MysqlDepartmentDao();    }}//具体的accessDao工厂class AccessDaoFactory implements IDaoFactory {    @Override    public IUserDao createUserDao() {        return new AccessUserDao();    }    @Override    public IDepartmentDao createDepartmentDao() {        return new AccessDepartmentDao();    }}//抽象dao接口interface IUserDao {    void insertUser(User user);    User queryUserById(int id);}interface IDepartmentDao {    void insertDepartment(Department department);    Department queryDepartmentById(int id);}//操作Mysql的dao层class MysqlUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Mysql数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Mysql数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "mysql测试");    }}class MysqlDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Mysql数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "mysql测试");    }}//操作Access的dao层class AccessUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Access数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Access数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "access测试");    }}class AccessDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Access数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "access测试");    }}
运行结果:

向Mysql数据库User表中插入一条数据:User{id=0, name='Lisa'}在Mysql数据库User表中查询一条id为0的数据向Mysql数据库Department表中插入一条数据:Department{id=0, name='财务部'}这里只需把new MysqlDaoFactory()改成new AccessDaoFactory()即可完成dao的切换向Access数据库User表中插入一条数据:User{id=0, name='Lisa'}在Access数据库User表中查询一条id为0的数据向Access数据库Department表中插入一条数据:Department{id=0, name='财务部'}

增加了部门dao后,其实上面就是抽象工厂模式了,抽象工厂模式是工厂模式的一种,专门用来解决多种产品的问题,例子中的具体产品就是MysqlUserDao/AccessUserDao和MysqlDepartmentDao/AccessDepartmentDao。

这样做的好处是方便进行整个产品系列的切换,如上面注释所说:只需把new MysqlDaoFactory()改成new AccessDaoFactory()即可完成dao的切换

工厂类在应用中的初始化只进行一次,它之后所生产的产品类型就决定下来了。另外一个好处是:它让具体的实例创建过程和客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体雷鸣也被具体工厂实现分离,不会出现在客户端代码中,所以上面例子的客户端所认识的只有IUserDao和IDepartmentDao,至于他们是用Mysql还是Access实现的就不知道了。


不过缺点还是有的:如果客户端程序类不只有一个,new MysqlDaoFactory()还是要改多次的,并且每添加一个表(比如说Agent),就意味着要做以下更改:增加IAgentDao、MysqlAgentDao、AccessAgentDao和修改IDaoFactory、MysqlDaoFactory、AccessDaoFactory,造成添加和改动的类过多,可以结合简单工厂模式进行优化:

public class Main {    public static void main(String[] args) {        User user = new User(0, "Lisa");        Department department = new Department(0, "财务部");        IUserDao accessUserDao = DaoFactory.createUserDao();        accessUserDao.insertUser(user);        accessUserDao.queryUserById(0);        IDepartmentDao accessDepartmentDao = DaoFactory.createDepartmentDao();        accessDepartmentDao.insertDepartment(department);        accessDepartmentDao.queryDepartmentById(0);    }}//用户实体类class User {    private int id;    private String name;    public User(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "User{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//部门实体类class Department {    private int id;    private String name;    public Department(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "Department{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}/** * 这里改成了简单工厂模式 * 去掉了IDaoFactory、MysqlDaoFactory、AccessDaoFactory * 改用switch判断该返回哪个dao * */class DaoFactory {    private static final String dbType = "mysql";   //access    public static IUserDao createUserDao() {        switch (dbType) {            case "mysql":                return new MysqlUserDao();            case "access":                return new AccessUserDao();            default:                return null;        }    }    public static IDepartmentDao createDepartmentDao() {        switch (dbType) {            case "mysql":                return new MysqlDepartmentDao();            case "access":                return new AccessDepartmentDao();            default:                return null;        }    }}//抽象dao接口interface IUserDao {    void insertUser(User user);    User queryUserById(int id);}interface IDepartmentDao {    void insertDepartment(Department department);    Department queryDepartmentById(int id);}//操作Mysql的dao层class MysqlUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Mysql数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Mysql数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "mysql测试");    }}class MysqlDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Mysql数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "mysql测试");    }}//操作Access的dao层class AccessUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Access数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Access数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "access测试");    }}class AccessDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Access数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "access测试");    }}

这样,客户端连Mysql和Access的字样都没有了,实现进一步的解耦,但这样会比之前抽象工厂多一个问题:如果增加一个Oracle数据库访问,就要在switch上添加分支了,不符合开闭原则,可以通过反射来解决:

package dao;public class Main {    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {        User user = new User(0, "Lisa");        Department department = new Department(0, "财务部");        IUserDao accessUserDao = DaoFactory.createUserDao();        accessUserDao.insertUser(user);        accessUserDao.queryUserById(0);        IDepartmentDao accessDepartmentDao = DaoFactory.createDepartmentDao();        accessDepartmentDao.insertDepartment(department);        accessDepartmentDao.queryDepartmentById(0);    }}//用户实体类class User {    private int id;    private String name;    public User(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "dao.User{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}//部门实体类class Department {    private int id;    private String name;    public Department(int id, String name) {        this.id = id;        this.name = name;    }    @Override    public String toString() {        return "dao.Department{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}/** * 这里改成了简单工厂模式 * 去掉了IDaoFactory、MysqlDaoFactory、AccessDaoFactory * 改用反射来决定调用哪个dao * */class DaoFactory {    private static final String packageName = "dao";    private static final String dbType = "Mysql";   //Access    public static IUserDao createUserDao() throws ClassNotFoundException, IllegalAccessException, InstantiationException {        String className = packageName + "." + dbType + "UserDao";        return (IUserDao) Class.forName(className).newInstance();    }    public static IDepartmentDao createDepartmentDao() throws ClassNotFoundException, IllegalAccessException, InstantiationException {        String className = packageName + "." + dbType + "DepartmentDao";        return (IDepartmentDao) Class.forName(className).newInstance();    }}//抽象dao接口interface IUserDao {    void insertUser(User user);    User queryUserById(int id);}interface IDepartmentDao {    void insertDepartment(Department department);    Department queryDepartmentById(int id);}//操作Mysql的dao层class MysqlUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Mysql数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Mysql数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "mysql测试");    }}class MysqlDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Mysql数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "mysql测试");    }}//操作Access的dao层class AccessUserDao implements IUserDao{    @Override    public void insertUser(User user){        System.out.println("向Access数据库User表中插入一条数据:" + user);    }    @Override    public User queryUserById(int id) {        System.out.println("在Access数据库User表中查询一条id为"+ id +"的数据");        return new User(id, "access测试");    }}class AccessDepartmentDao implements IDepartmentDao {    @Override    public void insertDepartment(Department department) {        System.out.println("向Access数据库Department表中插入一条数据:" + department);    }    @Override    public Department queryDepartmentById(int id) {        return new Department(0, "access测试");    }}

其实现在还是有违背开闭原则的,因为切换数据库访问还是需要更改工厂类的dbType然后再重新编译,我们可以通过配置文件来解决这个问题:

配置文件在文件夹resources里的config.xml:

<?xml version="1.0" encoding="UTF-8"?><config>    <databaseSetting>        <databasePackage>dao</databasePackage>        <databaseName>Mysql</databaseName>    </databaseSetting></config>
然后DaoFactory作以下改动就可以了:

/** * 这里改成了简单工厂模式 * 去掉了IDaoFactory、MysqlDaoFactory、AccessDaoFactory * 改用反射来决定调用哪个dao * */class DaoFactory {    private static String packageName;    // = "dao";    private static String dbType;         // = "Mysql";   //Access    static  {        File configFile = new File("resources/config.xml");        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();        DocumentBuilder builder;        try {            builder = dbf.newDocumentBuilder();            Document document = builder.parse(configFile);            packageName = document.getElementsByTagName("databasePackage").item(0).getFirstChild().getNodeValue();            dbType = document.getElementsByTagName("databaseName").item(0).getFirstChild().getNodeValue();        } catch (ParserConfigurationException e) {            e.printStackTrace();        } catch (SAXException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }    public static IUserDao createUserDao() throws ClassNotFoundException, IllegalAccessException, InstantiationException {        String className = packageName + "." + dbType + "UserDao";        return (IUserDao) Class.forName(className).newInstance();    }    public static IDepartmentDao createDepartmentDao() throws ClassNotFoundException, IllegalAccessException, InstantiationException {        String className = packageName + "." + dbType + "DepartmentDao";        return (IDepartmentDao) Class.forName(className).newInstance();    }}

来到这里,我们做到了通过修改配置文件即可切换数据库访问方式了O(∩_∩)O