浅谈设计模式之工厂模式
一.简介
工厂模式主要是为创建对象提供了接口.将对象的创建和使用分离开来.工厂模式按照《设计模式》中的提法分为三类:
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产品族
抽象工厂模式优缺点如下:
优点:很方便的切换产品族
缺点:在使用的时候,只能确定使用某一单一的产品族,扩展性差了.但是大多数场景如此
好了,大致了说明了下工厂模式一些个人理解,欢迎指教,对于抽象工厂模式个人认为用处很鸡肋,欢迎拍砖.
五.小结
在实际的编码过程中,千万不要生搬硬套,设计模式的出现是为了更好写代码,提交代码复用性,千万不要为了设计模式而设计模式.谢谢!