设计模式----抽象工厂模式

今天我们来介绍一种新的设计模式----抽象工厂模式。

大家可以复习一下之前学过的简单工厂模式和工厂方法模式:简单工厂模式      工厂方法模式

为了让大家理解的更加深刻,我先来举一个例子:

我们要帮一个公司写一个连接数据库以及配置操作的程序,这个公司一开始用的Oracel,后来又用了mysql,之后为了省钱干脆用Access当做数据库。

(这个例子我是瞎举的,现实中应该没有这样的公司)

这个公司总共换了两次数据库,我们就要改两次代码(因为不同的数据库语句不同并且可能有的方法也不同)

如果我们就是很简单的写最基本的数据访问程序的话,要修改起来是很麻烦的一件事,因为要在程序的很多地方动刀子,那么这样的程序就是丑陋的程序,

为了让我们的代码更加优美简洁,我们就有必要使用今天介绍的 抽象工厂模式

先来看看抽象工厂模式的UML类图

看上去有点复杂,其实非常好理解:

首先有一个抽象工厂的接口,要实现的接口方法是产生不同的产品

为了生产同种产品的不同形式(好比都是对接数据库的程序,但是数据库又有MySQL,Oracle等等......)

而AbstractProductA/B则是不同的产品(好比数据库访问用户表或者访问部门表)

继承它的子类ConcreateProduct(A/B)(1/2)则是不同数据库的不同产品。

我们再来看看代码:

Template:

抽象工厂代码:

public abstract class IFactory {
    public abstract IProductA createProductA();
    public abstract IProductB createProductB();
}

具体工厂1:

class ConcreteFactory1 extends IFactory{
    @Override
    public IProductA createProductA() {
        System.out.println("生产ProductA1");
        return new ProductA1();
    }

    @Override
    public IProductB createProductB() {
        System.out.println("生产ProductB1");
        return new ProductB1();
    }
}

具体工厂2:

class ConcreteFactory2 extends IFactory{
    @Override
    public IProductA createProductA() {
        System.out.println("生产ProductA2");
        return new ProductA2();
    }

    @Override
    public IProductB createProductB() {
        System.out.println("生产ProductB2");
        return new ProductB2();
    }
}

具体产品类我就不写了。

看看Client:

public static void main(String[] args) {
        IFactory iFactory1 = new ConcreteFactory1();
        iFactory1.createProductA();
        iFactory1.createProductB();

        IFactory iFactory2 = new ConcreteFactory2();
        iFactory2.createProductA();
        iFactory2.createProductB();
    }

控制台输出:

生产ProductA1
生产ProductB1
生产ProductA2
生产ProductB2

这也体现了对多态的灵活运用。

我们再来看一个具体的例子:

Example:

这个例子就是之前的那个省钱的公司,我们要帮这个公司改写我们之前丑陋的代码,运用抽象工厂模式实现对数据库MySQL或者Access的访问:

先来看看UML类图:

先来看看抽象工厂:

/**
 * @author 陈柏宇
 * 抽象工厂接口
 */

public interface IFactory {
    IUser createUser();
    IDepartment createDepartment();
}

class SqlServerFactory implements IFactory{
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment();
    }
}

class AccessFactory implements IFactory{
    @Override
    public IUser createUser() {
        return new AccessUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new AccessDepartment();
    }
}

看看IUser(对User的操作):

public interface IUser {
    void insert(User user);
    User getUserById(int id);
}

class SqlServerUser implements IUser{
    @Override
    public void insert(User user) {
        System.out.println("SQLServer插入一条User数据");
    }

    @Override
    public User getUserById(int id) {
        System.out.println("SQLServer根据id查询一条用户数据");
        return null;
    }
}

class AccessUser implements IUser{
    @Override
    public void insert(User user) {
        System.out.println("Access插入一条User数据");
    }

    @Override
    public User getUserById(int id) {
        System.out.println("Access根据id查询一条用户数据");
        return null;
    }
}

看看IDepartment(对Department的操作):

public interface IDepartment {
    void insert(Department department);
    Department getDepartmentById(int id);
}

class SqlServerDepartment implements IDepartment{
    @Override
    public void insert(Department department) {
        System.out.println("SQLServer插入一条Department数据");
    }

    @Override
    public Department getDepartmentById(int id) {
        System.out.println("SQLServer根据id查询一条Department数据");
        return null;
    }
}

class AccessDepartment implements IDepartment{
    @Override
    public void insert(Department department) {
        System.out.println("Access插入一条Department数据");
    }

    @Override
    public Department getDepartmentById(int id) {
        System.out.println("Access根据id查询一条Department数据");
        return null;
    }
}

Client端:

public static void main(String[] args) {
        User user = new User();
        Department department = new Department();

        //IFactory factory = new AccessFactory();
        IFactory factory = new SqlServerFactory();

        IDepartment iDepartment = factory.createDepartment();
        IUser iUser = factory.createUser();

        iDepartment.insert(department);
        iDepartment.getDepartmentById(1);

        iUser.insert(user);
        iUser.getUserById(1);
    }

如果客户端是这样的代码那么我们可以控制:

        //IFactory factory = new AccessFactory();
        IFactory factory = new SqlServerFactory();

来控制我们到底是操纵MySQL数据库还是Access

如果客户端就是按照这样的那么输出:

SQLServer插入一条Department数据
SQLServer根据id查询一条Department数据
SQLServer插入一条User数据
SQLServer根据id查询一条用户数据

否则输出:

Access插入一条Department数据
Access根据id查询一条Department数据
Access插入一条User数据
Access根据id查询一条用户数据

这样就大大化简了我们的操作。

但是这样就结束了吗?

事情还没有结束......

Reinforce:

抽象工厂模式它并不是十全十美的,还是优缺点的,如果这里我们想加入一个项目表Project

那么我们至少要添加三个类:IProject、SqlServerProject、AccessProject

还需要修改IFactory、SqlServerFactory、AccessFactory才可以完全实现

这实在是有点糟糕。

而且我们客户端程序不会只有一个,很多地方都在使用IUser,IDepartmen,这样的设计我们就要在实现工厂的地方都要修改,

有100处就修改100次,编程是门艺术,这样大批量的改动,显然是丑陋的做法。

 那么我们还可以用什么方法来改进抽象工厂呢?

我这里介绍一种我认为非常好的方法来改进它:用反射+配置文件实现数据访问

我们可以通过配置properties文件 + IO流读取文件中的内容获取我们使用的数据库类型,然后通过反射确定方法调用即可。

properties文件配置如下:

DB=SqlServer

客户端这样写:

public static void main(String[] args) {
        FileInputStream fis = null;
        String db = null;
        try {
            Properties pros = new Properties();
            fis = new FileInputStream("file.properties");
            pros.load(fis);  //加载对应的流文件
            //获取数据库类型
            db = pros.getProperty("DB");
            //获取全类名
            String name = "reinforce." + db + "Factory";
            //利用反射根据类名获取类
            Class<IFactory> clazz = (Class<IFactory>) Class.forName(name);
            //根据类获取对象
            IFactory iFactory = clazz.newInstance();

            IDepartment department = iFactory.createDepartment();

            department.getDepartmentById(1);
            department.insert(new Department());

            IUser user = iFactory.createUser();
            user.getUserById(1);
            user.insert(new User());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

输出:

SQLServer根据id查询一条Department数据
SQLServer插入一条Department数据
SQLServer根据id查询一条用户数据
SQLServer插入一条User数据

我们运用了反射获取到了SqlServerFactory的对象,想要获取AccessFactory对象只需要修改配置文件为:

DB=Access

这时候再运行客户端输出:

Access根据id查询一条Department数据
Access插入一条Department数据
Access根据id查询一条用户数据
Access插入一条User数据

我们利用反射实现了只用操纵配置文件就可以实现不同数据库的访问。

posted @ 2021-09-15 18:18  Apak陈柏宇  阅读(47)  评论(0编辑  收藏  举报