浅谈设计模式之工厂模式

浅谈设计模式之工厂模式

一.简介

​ 工厂模式主要是为创建对象提供了接口.将对象的创建和使用分离开来.工厂模式按照《设计模式》中的提法分为三类:

1.简单工厂模式 (严格来说 简单工厂不属于23种设计模式之一)
2.工厂方法模式
3.抽象工厂模式

​ 那这三种模式具体有啥用处和区别,通过下方的小例子我们来分析下:

二.简单工厂模式
引子:

我们使用jdbc来操作数据库,得与数据库连上,比方说我们查询一个用户表的数据,代码如下:

package com.jk1123.factory.simple;

import com.jk1123.factory.env.User;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

public class Demo01 {
    public static void test01() throws Exception {

        String jdbc_driverClass = "com.mysql.jdbc.Driver";
        String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
        String jdbc_username = "root";
        String jdbc_password = "root";
        //注册驱动
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        //预处理
        PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
        //执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        //解析结果集
        List<User> users=new ArrayList<>();
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            User user = new User();
            user.setId(id);
            user.setUsername(username);
            user.setPassword(password);
            users.add(user);
        }

        resultSet.close();
        preparedStatement.close();
        connection.close();

        for (User user : users) {
            System.out.println(user);
        }
    }
    public static void test03() throws Exception {
        String jdbc_driverClass = "com.mysql.jdbc.Driver";
        String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
        String jdbc_username = "root";
        String jdbc_password = "root";
        //注册驱动
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        //获得连接
        PreparedStatement preparedStatement = connection.prepareStatement("insert INTO user VALUES (null,?,?)");
        preparedStatement.setString(1,"小明");
        preparedStatement.setString(2,"123456");
        //执行sql
        preparedStatement.execute();
    }
    
}

在上述代码中,我们发现我们需要连接上数据库才能进行跟数据库的交互,而创建数据库连接代码,出现大量的冗余,

而且我们还有可能出现,连接oracle数据库的可能:

public static void test02() throws Exception {
        String jdbc_driverClass = "oracle.jdbc.driver.OracleDriver";
        String jdbc_url = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
        String jdbc_username = "system";
        String jdbc_password = "123456";
        //注册驱动
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);//预处理
        PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
        //执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        //解析结果集
        List<User> users=new ArrayList<>();
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            User user = new User();
            user.setId(id);
            user.setUsername(username);
            user.setPassword(password);
            users.add(user);
        }

        resultSet.close();
        preparedStatement.close();
        connection.close();

        for (User user : users) {
            System.out.println(user);
        }
    }

代码这么写下去看起来就很恶心了,于是乎我们可以将创建连接的代码抽取出来,我们可以再创建一个类,专门负责创建连接:

package com.jk1123.factory.simple;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class SimpleConnectionFactory {
    public Connection newConnection(int type) throws Exception {
        if (type==1){
            String jdbc_driverClass = "com.mysql.jdbc.Driver";
            String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
            String jdbc_username = "root";
            String jdbc_password = "root";
            Class.forName(jdbc_driverClass);
            //获得连接
            Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
            return connection;
        }else if (type==2){
            String jdbc_driverClass = "oracle.jdbc.driver.OracleDriver";
            String jdbc_url = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
            String jdbc_username = "system";
            String jdbc_password = "123456";
            Class.forName(jdbc_driverClass);
            //获得连接
            Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
            return connection;
        }else{
            return null;
        }

    }
}

当我们再需要一个连接时候,只需要调用工厂方法帮我们创建即可,无需再关系连接创建的过程:

package com.jk1123.factory.simple;

import com.jk1123.factory.env.User;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class Demo02 {
    public static void test01() throws Exception {
        //获得连接
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        SimpleConnectionFactory connectionFactory = new SimpleConnectionFactory();
        //为1的时候返回的是mysql的连接
        Connection connection = connectionFactory.newConnection(1);
        //下面逻辑代码省略了
    }
    public static void test02() throws Exception {
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        SimpleConnectionFactory connectionFactory = new SimpleConnectionFactory();
        //为2的时候返回是oracle的连接
        Connection connection = connectionFactory.newConnection(2);
        //下面逻辑代码省略了
    }
    
}

上面代码可以发现,使用工厂模式使我们可以将创建对象的职责交给了工厂,让工厂来帮助我们封装创建对象的过程,如果创建过程有啥改变我们只需要改变对应的工厂即可,这是一种思维的转变.

术语名称叫做依赖倒置,翻译过来的意思就是,当你需要一个对象的时候,不要再自己new对象,而是交给别的程序来帮你创建,使用方法 只要调用工厂方法即可.工厂内部怎么实现与使用方无关,这种思想也就是javaee开发中常用spring框架的ioc思想.

从上面小例子就是一种简单的工厂模式,我们发现工厂模式有如下特征:

1.工厂类—负责生产对象的类—上述例子中SimpleConnectionFactory类

2.抽象产品接口—产品的接口—java.sql.Connection接口

3.具体产品类—oracle或者mysql的接口实现类

3.客户端类—使用代码方—Demo1

简单工厂模式优缺点:

优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。

缺点:很明显工厂类集中了所有实例的创建逻辑,违反了**开闭原则**,所谓开闭原则就是:

(1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。

(2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。

而对于咱们现在简单工厂来讲,当我需要额外增加其他类型的数据库连接 比如 sqlServer数据库的连接时,就需要去改动工厂类,扩展不易.

三.工厂方法模式

​ 意识到上述简单工厂模式缺点,我们尝试将代码改成下方:

package com.jk1123.factory.factorymethod;

import java.sql.Connection;

public interface ConnectionFactory {
    Connection newConnection() throws Exception;
}

我们先声明一个工厂接口.确定工厂必须具有创建连接的能力.

package com.jk1123.factory.factorymethod;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * 声明mysql的链接工厂实现类
 */
public class MySQLConnectionFactory implements ConnectionFactory {
    @Override
    public Connection newConnection() throws Exception {
        String jdbc_driverClass = "com.mysql.jdbc.Driver";
        String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
        String jdbc_username = "root";
        String jdbc_password = "root";
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        return connection;
    }
}
package com.jk1123.factory.factorymethod;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * 声明oracle的链接工厂实现类
 */
public class OracleConnectionFactory implements ConnectionFactory{
    @Override
    public Connection newConnection() throws Exception {
        String jdbc_driverClass = "oracle.jdbc.driver.OracleDriver";
        String jdbc_url = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
        String jdbc_username = "system";
        String jdbc_password = "123456";
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        return connection;
    }
}

上述代码中我们又声明了两个具体的工厂实现类,一个具体的工厂只负责生产一种类型的链接.

package com.jk1123.factory.factorymethod;

import java.sql.Connection;

public class Demo01 {
    public static void test01() throws Exception {
        //获得连接
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new MySQLConnectionFactory();
        //为1的时候返回的是mysql的连接 而且不再创建对象了 代码进一步清爽了
        Connection connection = connectionFactory.newConnection();
        //下面代码省略了
    }
    public static void test02() throws Exception {
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new OracleConnectionFactory();
        //为1的时候返回的是mysql的连接 而且不再创建对象了 代码进一步清爽了
        Connection connection = connectionFactory.newConnection();
        //下面代码省略了
    }
    public static void main(String[] args) throws Exception {
        test01();
    }
}

然后在具体的使用去使用,这种模式下,当我们需要一种类型的链接时,只需要去创建对应的工厂就行了,大大提高了可扩展性,而且不需要修改原有实现代码.

上述修改我们使用的是工厂方法模式,该模式有以下特征:

1.工厂接口—ConnectionFactory—用来约束一个工厂至少有啥功能

2.工厂实现类—OracleConnectionFactory—MySQLConnectionFactory–用来实际创建链接的工厂

3.产品接口—Connection

4.客户端—demo01

通过例子中,我们可以看出工厂方法模式优缺点:

优点:工厂方法模式在开闭原则实现的比较好,比较容易扩展新的实现

缺点:工厂方法模式代码冗余,新增额外的工厂代码量比较大.

总的来说,工厂方法模式虽然增加了很多代码,也更加的复杂,但是其带来的可扩展性却是无与伦比.所以总的来说利大于弊.值得提倡使用

四:抽象工厂模式

​ 抽象工厂模式是啥呢,这里得首先说明咱们接下来想做什么,我们都知道,在我们使用连接对象的时候,最好使用连接池方案,将连接缓存起来,减少不断创建连接的过程,此时我们需要创建自带连接池方案的连接,这时候无论是mysql的连接 还是oracle的连接,都需要创建被增强过的连接池方案的连接对象,那么我们的工厂类怎么做呢?

难道再声明一个 带连接池方案的接口工厂,no no no 那样的话会导致我们接口和爆炸的,我们可以进行如下改造:

package com.jk1123.factory.abstractfactory;

import java.sql.Connection;

public interface ConnectionFactory {
    Connection newConnection() throws Exception;
    //在原来接口的基础 新增方法
    Connection newPooledConnection() throws Exception;
}

package com.jk1123.factory.abstractfactory;


import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.DriverManager;

public class MySQLConnectionFactory implements ConnectionFactory {
    private ComboPooledDataSource dataSource=new ComboPooledDataSource();
    {
        String jdbc_driverClass = "com.mysql.jdbc.Driver";
        String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
        String jdbc_username = "root";
        String jdbc_password = "root";
        try {
            dataSource.setDriverClass(jdbc_driverClass);
            dataSource.setJdbcUrl(jdbc_url);
            dataSource.setUser(jdbc_username);
            dataSource.setPassword(jdbc_password);
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }
    @Override
    public Connection newConnection() throws Exception {
        String jdbc_driverClass = "com.mysql.jdbc.Driver";
        String jdbc_url = "jdbc:mysql://127.0.0.1:3306/day08";
        String jdbc_username = "root";
        String jdbc_password = "root";
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        return connection;
    }

    @Override
    public Connection newPooledConnection() throws Exception {
        return dataSource.getConnection();
    }
}

package com.jk1123.factory.abstractfactory;


import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.DriverManager;

public class OracleConnectionFactory implements ConnectionFactory{
    private ComboPooledDataSource dataSource=new ComboPooledDataSource();
    {
        String jdbc_driverClass = "oracle.jdbc.driver.OracleDriver";
        String jdbc_url = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
        String jdbc_username = "system";
        String jdbc_password = "123456";
        try {
            dataSource.setDriverClass(jdbc_driverClass);
            dataSource.setJdbcUrl(jdbc_url);
            dataSource.setUser(jdbc_username);
            dataSource.setPassword(jdbc_password);
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }
    @Override
    public Connection newConnection() throws Exception {
        String jdbc_driverClass = "oracle.jdbc.driver.OracleDriver";
        String jdbc_url = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
        String jdbc_username = "system";
        String jdbc_password = "123456";
        Class.forName(jdbc_driverClass);
        //获得连接
        Connection connection = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
        return connection;
    }

    @Override
    public Connection newPooledConnection() throws Exception {
        return dataSource.getConnection();
    }
}

上述代码中,我们发现在原来的接口基础又扩展了方法,也就是声明了一个工厂要具有的功能,然后在咱们工厂实现类去实现多出来的方法,防止类和接口结构爆炸.做出一定的妥协.

package com.jk1123.factory.abstractfactory;

import java.sql.Connection;

public class Demo01 {
    public static void test01() throws Exception {
        //获得连接
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new MySQLConnectionFactory();
        Connection connection = connectionFactory.newConnection();
        //下面代码省略了
    }
    public static void test02() throws Exception {
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new MySQLConnectionFactory();
        Connection connection = connectionFactory.newPooledConnection();
        //下面代码省略了
    }
    public static void main(String[] args) throws Exception {
        test01();
    }
}

package com.jk1123.factory.abstractfactory;

import java.sql.Connection;

public class Demo02 {
    public static void test01() throws Exception {
        //获得连接
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new OracleConnectionFactory();

        Connection connection = connectionFactory.newConnection();
        //下面代码省略了
    }
    public static void test02() throws Exception {
        //不再关心具体怎么设置的参数 而是通过工厂直接给返回一个连接
        ConnectionFactory connectionFactory = new OracleConnectionFactory();

        Connection connection = connectionFactory.newPooledConnection();
        //下面代码省略了
    }
    public static void main(String[] args) throws Exception {
        test01();
    }
}

在使用的时候,我们规定一个我们只能使用某种类型的工厂,一旦切换就切换一个整体,而实际开发也正是如此,我们不会再使用的时候使用两套不同方案,如例子中,既使用mysql 又使用oracle.所以还是可以接受.

上述代码中给出方案就是抽象工厂模式,有如下特征:

1.有一个抽象工厂接口—在接口声明工厂应该具有的功能

2.有具体的工厂实现类----oracle的工厂和mysql的工厂

3.客户端使用类

4.具体的产品类,这个例子咱们使用了 c3p0的连接池方案

其实抽象工厂模式涉及到了 几个概念,

产品树:指的具有类似功能的一系列实现类 比如mysql连接类 或者c3p0的包装过的实现类

产品族:oracle类型的连接或者是mysql的连接

而使用抽象工厂模式的目的是为了 方便的切换产品族,该例子中指的mysql或者oralce产品族

抽象工厂模式优缺点如下:

优点:很方便的切换产品族

缺点:在使用的时候,只能确定使用某一单一的产品族,扩展性差了.但是大多数场景如此

好了,大致了说明了下工厂模式一些个人理解,欢迎指教,对于抽象工厂模式个人认为用处很鸡肋,欢迎拍砖.

五.小结

​ 在实际的编码过程中,千万不要生搬硬套,设计模式的出现是为了更好写代码,提交代码复用性,千万不要为了设计模式而设计模式.谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值