工厂方法模式
title: 工厂方法模式
date: 2019-03-04 20:33:34
简介
工厂方法模式(Factory Method),又称虚构造器。在《设计模式》中如此定义:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
工厂方法模式中包含四个核心角色:抽象工厂、具体工厂、抽象产品、具体产品。
工厂方法模式示例图如下:
图片1.png-
Creator
类为抽象工厂类,包含一个虚方法FactoryMethod()
,具体由子类实现;包含一个类方法AnOperation()
,该方法中通过调用FactoryMethod()
类获取产品。 -
ConcreteCreator
类为具体工厂类,继承于Creator
类,实现了FactoryMethod()
方法,该方法实例化并返回一个ConcreteProduct
类。 -
Product
类为抽象产品类。 -
ConcreteProduct
类为具体产品类。
理解工厂方法模式
理解之前,我们先来讲一个小明买衣服的故事。
小明买衣服
假设,小明需要一件衣服。
同时,夏天小明需要的衣服是短袖,冬天小明需要的衣服是棉袄。
但是呢,小明不会自己织造衣服,更不理解衣服织造的过程,他只是想得到这么一件衣服。
所以,他需要向生产衣服的工厂购买衣服。假设工厂A生产短袖、工厂B生产棉袄。那么,夏天小明需要向工厂A购买短袖,冬天小明需要向工厂B购买棉袄。
甚至,小明还需要毛衣、外套,那他只要在需要时,向对应工厂C(生产毛衣)、工厂D(生产外套),购买毛衣、外套即可。
换而言之,小明只需要更换相应的工厂就能得到相应的衣服(产品)。
由此及彼
其实,小明买衣服的过程就是工厂方法模式的应用。短袖、棉袄等是具体的产品,继承与衣服抽象类。工厂A、B、C、D则是具体的工厂,继承与抽象工厂。每个工厂返回对应的、独有的产品实例,就是工厂方法的作用。
在工厂方法这种模式下,不管有多少新增产品,只要创建出生产对应产品的具体工厂实例即可。完全符合开闭原则(允许扩展,不允许修改)。
虽然,用户不需要知道产品的生产过程,但是,用户必须知道对应的具体工厂,才能得到对应的产品。而且,想要获得一个简单的产品,必须创建一个具体工厂,这也是工厂方法模式的一个缺陷。
或许,没有结合实际,你还不清楚这个模式究竟有什么用处。
那接下来,我将结合java
代码进行演示,并用jdbc
的例子来说明该模式的作用。
代码演示(java)
首先,我们先创建出抽象产品和抽象工厂类:
// 抽象产品
public abstract class Product {
public abstract void printInfo();
}
// 抽象工厂
public abstract class Creator {
// 工厂方法
public abstract Product createProudct();
// 调用createProduct()虚方法,实例化产品
public void printProductInfo() {
Product product = createProudct();
System.out.println("生产出具体产品");
product.printInfo();
}
}
然后,我们创建具体产品A,重载并实现抽象产品类中的虚方法:
public class ConcreteProduct_A extends Product{
public String name;
public int price;
public ConcreteProduct_A(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public void printInfo() {
System.out.printf("this is concrete product a, product's name is %s, product's price is %d.\n", name, price);
}
}
注意:此时用户并不知道产品A是如何实现的,也不知道产品A的类名,用户只知道获得一件产品之后可以打印产品信息。因此,用户无法通过new ConreteProduct_A()
来获得这个产品对象。
接着,我们创建生产产品A的具体工厂A:
public class ConcreteCreator_A extends Creator {
@Override
public Product createProudct() {
return new ConcreteProduct_A("productA", 12);
}
}
由于工厂A继承于抽象工厂,便可以调用printProductInfo()
方法,来打印自己生产的产品的信息。
同时,告诉用户工厂A ConcreteCreator_A
可以生产产品A,只要调用工厂A的createProudct()
方法即可。于是,用户便可以如下获取并使用产品A:
Creator creatorA = new ConcreteCreator_A();
Product productA = creatorA.createProudct();
productA.printInfo();
但是问题来了,为什么不直接告诉用户产品A的类名ConcreteProduct_A
,用户直接new ConcreteProduct_A()
,不也能得到产品A的实例吗?
其实,真正的工厂方法并没有那么简单,不是直接new ConcreteProduct_A()
返回一个产品实例就完事的,而是需要对产品进行相应的“封装”才返回给用户的。
举个栗子
实际开发中,工厂方法模式对于普通用户、普通应用是没有太大作用的,但是对于一些框架的开发是十分有用的。
JDBC数据库连接
以JDBC
为例,我们用java
连接MySql
数据库的常规代码如下:
import java.sql.Connection; // jdk数据库连接器
import java.sql.DriverManager; // jdk驱动加载管理器
import java.sql.ResultSet; // jdk数据接收器
import java.sql.Statement; // jdk数据sql执行对象
import com.mysql.jdbc.Driver; // 导入mysql的jdbc包
// 1、首先咱们把mysql的驱动交给java管理
DriverManager.registerDriver(new Driver());
// 2、建立数据库连接,并得到数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://url", "root", "xxxxxx");
// 3、得到执行sql的对象
Statement sta = conn.createStatement();
在这个过程中,java.sql.Connection
和java.sql.Driver
是java
提供给数据库方的接口。java
提供这种规范,要求数据库方必须实现这两个接口,才能让java
连接到数据库。同时,由于是接口,用户不能实例化,只有通过工厂方法才能得到实例对象。
java.sql.Connection
相当于抽象产品,java.sql.Driver
相当于抽象工厂。com.mysql.jdbc.Driver
相当于产生具体产品(mysql
连接)的具体工厂。而至于具体产品,用户甚至不需要知道它的类名,只需要知道Connection
接口中定义了哪些可以使用的方法即可。
至于数据库方如何实现java
与自身数据库的连接,具体过程就更不需要用户知道。用户只需要知道通过com.mysql.jdbc.Driver
这个具体工厂,可以得到连接mysql
的连接类即可。然后,通过这个连接类(产品),用户便可以与mysql
数据库进行交互了。
由于,市面上不止mysql
一家数据库,还有SQL Server
、Oracle
等不错的数据库。如果用户想用其它数据库,那就跟小明在不同时空下想购买不同衣服一样,只需要找到对应的具体工厂(数据库驱动类),就能得到具体产品(数据库连接类)。这两个都由各数据库来实现。
而用户根据自身情况从不同的具体工厂(数据库驱动类),获取不同的产品,只需要导入并注册不同的数据库驱动类即可,其它都不需要修改,示例博客。
深入源码
深入java.sql
和com.mysql.jdbc
,找到工厂方法getConnection()
的源码在java.sql.DriverManage
类中:
Connection con = aDriver.driver.connect(url, info);
这行代码调用注册的数据库驱动类中的connect()
方法,于是再找到com.mysql.jdbc.Driver
类的父类com.mysql.jdbc.NonRegisteringDriver
(实现了java.sql.Driver
接口)中的connect()
方法,如下:
public Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), "08001", (ExceptionInterceptor)null);
} else if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {
return this.connectLoadBalanced(url, info);
} else if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {
return this.connectReplicationConnection(url, info);
} else {
Properties props = null;
if ((props = this.parseURL(url, info)) == null) {
return null;
} else if (!"1".equals(props.getProperty("NUM_HOSTS"))) {
return this.connectFailover(url, info);
} else {
try {
com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
return newConn;
} catch (SQLException var6) {
throw var6;
} catch (Exception var7) {
SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001", (ExceptionInterceptor)null);
sqlEx.initCause(var7);
throw sqlEx;
}
}
}
}
可以看到工厂方法并非简单返回一个具体产品(数据库连接类)即可,而是需要进行多层的包装和处理,才能返回给用户。
所以,这就解释了为什么不直接告诉用户com.mysql.jdbc.Connection
类,让用户自己实例化具体产品(new com.mysql.jdbc.Connection("")
),而是要通过具体工厂来返回这个实例化的具体产品。当然,其原因不止如此。。。
总而言之,工厂方法模式在框架开发、多人协作的方面有着广泛的应用,大大降低了各模块之间的耦合性。