引言
为什么要引入工厂方法模式?
前文已经详细的讲解了简单工厂模式,既然已经有了简单工厂模式,为什么还要有工厂方法模式呢?
相信看完前文已经明确的知道简单工厂模式有以下不足:
- 把实例的创建(在这里实际是指产品类的实例化)都放在了工厂类中,这样做的结果是非常危险的,一旦这个类出现任何问题,那么整个系统就game over了;
- 不符合设计模式思想的“开-闭”原则。“开-闭”原则的核心就是在不更改现有系统的结构上实现新的功能,工厂类明显不符合,一旦增加新的产品,工厂类的原有代码肯定是需要更改的,结构肯定会有变化的。
核心的工厂类不再负责所有的产品的创建,将具体创建的工作交给子类去做,换句话说,也就是原有的工厂类变成了一个抽象工厂的角色,仅负责给出具体工厂子类必须实现的接口。
工厂方法模式定义
工厂方法模式是类的创建模式,又被称为虚拟构造子模式或者多态性工厂模式。它的用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。
工厂方法模式结构与角色
在说它的结构与角色之前还是先看看类图,这样稍微明显易懂些。
从上图可以看出,工厂方法模式有有以下这些角色:
- 抽象工厂(Creator)角色:工厂方法模式核心,与应用程序无关,在实际的系统中,这个角色常常使用抽象Java类实现;
- 具体工厂(Concrete Creator)角色:实现抽象工厂接口,该角色含有与应用密切相关的逻辑,并且受到应用程序的调用以创建产品对象;
- 抽象产品(Product)角色
- 具体产品(Concrete Product)角色
自定义工厂方法模式
照样只是给一个简单的demo,给出一个工厂方法模式的模板。
- 需求:某一个商业软件产品需要支持Mysql和Oracle数据库,给出一个简单的设计,保证系统可以根据客户端的需要,随时向Mysql和Oracle数据库引擎发起查询请求;
- 抽象设计:
- 首先确定使用哪种设计模式比较合适,很明显,这个需求使用工厂方法模式来实现会更好;
- 确定好设计模式之后,接下来该抽象出各个角色了:
- 抽象工厂角色:抽象出查询类QueryRunner;
- 具体工厂角色:mysql查询和oracle查询;
- 抽象产品角色:查询结果集,ResultSet;
- 具体产品角色:mysql查询返回的ResultSet,oracle查询返回的ResultSet
- 类图:
- 具体代码实现:
QueryRunner:MySQLQueryRunner:public abstract class QueryRunner { public ResultSet run() throws Exception { Connection connection = createConnection(); String sql = createSql(); return run(connection, sql); } protected abstract Connection createConnection() throws ClassNotFoundException, SQLException; protected abstract String createSql(); protected abstract ResultSet run(Connection connection, String sql) throws SQLException; }
OracleQueryRunner:public class MysqlQueryRunner extends QueryRunner { String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "asdfasdf"; protected Connection createConnection() throws ClassNotFoundException, SQLException { Class.forName(driver); Connection connection = DriverManager.getConnection(url, user, password); return connection; } protected String createSql() { return "select * from user"; } protected ResultSet run(Connection connection, String sql) throws SQLException { Statement statement = connection.createStatement(); return statement.executeQuery(sql); } }
DataBaseClient:public class OracleQueryRunner extends QueryRunner { private String driver = "oracle.jdbc.driver.OracleDriver"; String url = "jdbc:oracle:thin:@127.0.0.1:1521:test"; String user = "root"; String password = "asdfasdf"; protected Connection createConnection() throws ClassNotFoundException, SQLException { Class.forName(driver); Connection connection = DriverManager.getConnection(url, user, password); return connection; } protected String createSql() { return "select * from user"; } protected ResultSet run(Connection connection, String sql) throws SQLException { Statement statement = connection.createStatement(); return statement.executeQuery(sql); } }
public class DataBaseClient { private static QueryRunner queryRunner; public static void main(String[] args) throws Exception { queryRunner = new OracleQueryRunner(); ResultSet resultSet = queryRunner.run(); //操作resultSet,这里的resultSet就是具体的产品 } }
工厂方法模式在Java中的应用
在Collection中的应用
相信看过jdk源码的都java的Collection吧,在说Collection之前还是先说说聚集吧。
何谓聚集?
Java聚集是Java 1.2提出来的,称多个对象聚在一起形成的总体为聚集,聚集对象时能够包容一组的容器对象。
继续说工厂模式在Collection中的应用,Collection接口规定所有实现接口的子类必须要提供一个iterator方法,返回一个Iterator类型的对象,可以看出iterator方法就是一个工厂方法。
URL与URLConnection的应用
- URL类简要介绍:
URL类代表一个Uniform Resource Locator,换句话说也就是互联网资源的一个指针。互联网的一个资源,可能是一个简单的文件、目录,也可以是一个更加复杂的对象。
- 如何创建URL对象:
创建一个URL对象很简单,主需要将一个合法的URL传入URL的构造方法中即可,代码如下:
URL url = new URL("http://www.baidu.com");
- 工厂方法模式分析:
URL对象提供了一个叫做openConnection()的工厂方法:
public URLConnection openConnection() throws java.io.IOException { return handler.openConnection(this); }
该方法返回URLConnection对象,该对象代表一个与远程对象的连接。同时,URLConnection是所有的代表应用系统与一个URL的连接对象的基类,使用URLConnection可以对任意一个URL进行读写操作。
看到这里可能还是很蒙圈,来个类图吧,相信看了类图就可以一目了然了。
由类图可以看出来:
- 工厂类:URL;
- 抽象产品类:URLConnection
- 具体产品类:HttpURLConnection、JarURLConnection
- 简单实例分析获取互联网资源步骤:
工厂方法模式在许多框架都有明显的应用,在这里不一一做讲解。有兴趣的可以自行阅读源码。public class URLTest { @Test public void run() throws Exception { URL url = new URL("http://www.baidu.com"); URLConnection connection = url.openConnection(); BufferedReader readerIn = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; while ((inputLine = readerIn.readLine()) != null) { System.out.println(inputLine); } //不要忘了关闭流 readerIn.close(); } }
可以看出,该例子在运行时的活动顺序如下:
- 创建一个以“http://www.baidu.com”为目标的URL对象;
- 调用URL对象的openCollection()方法,得到一个“http://www.baidu.com”的远程连接对象;
- 客户端调用URLConnection对象的getInputStream()方法读入远程URL的数据(运行时会打印出“http://www.baidu.com”页面的全部HTML源代码)