欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-08-04 08:00:21
举个栗子 问题描述 模拟访问数据库“新增用户”和“得到用户”,用户类假设只有 ID和Name 两个字段。 简单实现 User SqlServerUser 测试 测试结果 存在问题 如果需要连接别的数据库,那么这个写法无法扩展,下面使用 工厂方法模式 实现 工厂方法模式实现 IUser SqlServ ......

举个栗子

问题描述

模拟访问数据库“新增用户”和“得到用户”,用户类假设只有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,解除分支判断带来的耦合。

总结

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

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

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