简介
上一篇我们已经说了,简单工厂模式有一个天生的弊端,即所有的产品都出自一个工厂类,而且在创建产品的时候,必须以参数的形式告诉工厂我要创建哪个产品。这种设计在产品类比较少,而且产品种类更改不频繁的时候,勉强还可以应付,但是我们可以试想假如我们的产品类很多,有成百上千个,并且后前还会逐步添加,而且暂时还不知道要添加什么产品,这样我们的工厂类应该怎么写?要写成百上千个if else吗?要在每次添加产品的时候去修改工厂类吗?这样显然是一种很恶心的模式,所以我们的工厂方法模式就应运而生。
概念
工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
可以看到工厂方法模式中定义了一个工厂接口,而具体的创建工作推迟到具体的工厂类,它是对简单工厂模式中的工厂类进一步抽象化,从而产生一个工厂类的抽象和实现体系,从而弥补简单工厂模式对修改开放的诟病。
类图
可以看到,上面右半部分是产品抽象和实现体系,左半部分是工厂抽象和实现体系,其中工厂体系依赖于产品体系,每一个工厂负责创造一种产品,这就省去了简单工厂中的elseif判断,又客户端决定实例化一个特定的工厂去创建相应的产品。
角色
抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
案例演示
1、一个产品接口
/**
* 一个产品的抽象接口(定义产品共性)
*/
public interface IProduct {
public void action();
}
2、若干个产品类
/**
* 产品一:汽车
*/
public class Car implements IProduct {
public void action() {
System.out.println("汽车在路上跑!");
}
}
/**
* 产品二:飞机
*/
public class Airplane implements IProduct {
public void action() {
System.out.println("飞机在天上飞!");
}
}
3、一个工厂接口
/**
* 一个工厂接口(用于抽象所有的接口特性)
*/
public interface Factory {
public IProduct createProduct();
}
4、若干个工厂类
/**
* 产品工厂类一:汽车工厂
*/
public class FactoryCar implements Factory {
public IProduct createProduct() {
return new Car();
}
}
/**
* 产品工厂二:飞机工厂
*/
public class FactoryAirplane implements Factory {
public IProduct createProduct() {
return new Airplane();
}
}
5、测试类
public class App {
public static void main(String[] args) {
//创建一个汽车工厂
Factory factory = new FactoryCar();
Car car = (Car) factory.createProduct();
car.action();
//创建一个飞机工厂
factory = new FactoryAirplane();
Airplane airplane = (Airplane) factory.createProduct();
airplane.action();
}
}
下面看看简单工厂与工厂方法的对比
我们可以看出工厂方法与简单方法唯一的不同之处就在于工厂创建模块,简单工厂是直接一个工厂类创建了所有的产品,产品固定且少的时候适宜用简单工厂模式;而工厂方法模式只是在简单工厂模式的基础上对工厂进行了抽象,让各个产品的生产对应相应的工厂,这样在有新产品需要添加时,只需要开发一个对应的产品和产品工厂,无需对原有的其他代码进行改动。
开发中的案例
不知大家在开发的时候注意到在链接数据库JDBC的API设计模式没有。
众所周知,为了统一各个数据库操作的标准,于是有了JDBC的API,它用于给我们这种被称作只会使用现成的东西的程序猿,提供一系列统一的,标准化的操作数据库的接口。其实JDBC的各个类或接口,就是我们操作数据库的过程中各个协助者的抽象,这样的设计是为了让我们对数据库的操作依赖于抽象,还记得我们在设计模式总纲中提到的一句话吗,用抽象构建框架,用细节扩展实现。
JDBC API(即抽象的接口或类)就是整个数据库操作的框架,而各个数据库的驱动就是那些细节。而我们的操作依赖于JDBC API,而不是任何一个具体数据库的细节。
JDBC是如何统一了数据库世界的呢?其实最主要的就是靠两个接口,就统一了世界。。
来看第一个接口Driver源码
package java.sql;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
/**
* The interface that every driver class must implement.
*/
public interface Driver {
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
}
我们可以看到,这个接口中的connect方法,获取数据库连接。翻译过来是这是一个任何驱动类都必须实现的接口。多么霸气啊。也就是每个数据库厂商都必须实现这个接口来提供JDBC服务,即java数据库连接服务,来方便程序猿对数据库应用编程。
我们先忽略掉下面的五个方法,第一个方法毫无疑问是这个接口中相对而讲最重要的方法了,即创造一个数据库连接,虽然方法名称是connect,但是我觉得这个方法完全可以改为createConnection。
提到Connction,这个接口我们一定不陌生,我们截取它的一部分源码
package java.sql;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* <P>A connection (session) with a specific
* database. SQL statements are executed and results are returned
* within the context of a connection.
* <P>
*/
public interface Connection extends Wrapper {
Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql) throws SQLException;
}
以上便是Connection接口,这里只留下了两个方法,这两个方法相信各位读者都非常熟悉,它们都是我们最经常用的方法之二。
以上两个接口作为JDBC API的一部分,它们相当于告诉了数据库生产厂商两个要求。
第一,数据库厂商要提供一个数据库驱动类,它的作用可以是可以创造数据库连接,而这个数据库连接向上转型为我们JDBC的Connection。
第二,数据库厂商要提供一个数据库连接的实现类,这个实现类可以执行具体数据库的各个操作,比如帮我们执行SQL,返回执行结果,关闭连接等等。
我们都知道mysql的驱动类位于com.mysql.jdbc.Driver,而mysql的connection实现类也在这个包中,名称是ConnectionImpl,而相应的oracle也有驱动类,位于oracle.jdbc.driver.OracleDriver,相应的oracle也有connection实现类,位于oracle.jdbc.OracleConnectionWrapper。一般每个数据库都会有一个Connection的扩展接口,这个接口的作用是提供使用者针对当前数据库特殊的操作。
我们来看一个类图吧
我们对比上面标准的工厂方法模式,就会发现它们的关系不正是工厂方法模式吗?
工厂方法模式就是提供一个抽象的工厂,一个抽象的产品,在上述当中相当于Driver(数据库连接工厂)和Connection(抽象产品),实现的一方需要提供一个具体的工厂类(比如mysql驱动)和一个具体的产品(比如mysql数据库连接)。
工厂方法模式的好处和适用的场景都相对比较好理解。
从类关系上来说,它可以让客户端与具体的工厂与产品解耦,从业务角度来说,它让客户端与具体的产品解耦。
适用的场景就是我们需要一个产品帮我们完成一项任务,但是这个产品有可能有很多品牌(像这里的mysql,oracle),为了保持我们对产品操作的一致性,我们就可能要用到工厂方法模式。
另外在附上另一个java中有关工厂方法模式的案例
是不是很明显(上图来自于百度)