工厂方法模式

2016/08/20 设计模式

定义

工厂方法模式(英语:Factory method pattern)是一种实现了”工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是”定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。

类图

工厂方法模式类图 从上图可以看出4种类,抽象工厂,具体工厂,抽象产品,具体产品;回忆下简单工厂,貌似比他多了一个抽象工厂对吧。下面我们用列出java代码实现来看看他和简单工厂的区别。

java代码实现

抽象产品

public interface Product {
    void method();
}

具体产品

public class ProductA implements Product {
    @Override
    public void method() {
        System.out.println("productA 的方法");
    }
}

抽象工厂

public interface Factory {
    Product createProduct();
}

具体工厂

public class ConcreteFactoryA implements Factory {
    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

那么我们有新的产品的时候只需要新增一个具体的工厂类,一个具体的产品类 例如:ConcreteFactoryBProductB来生产相应的产品。这样就省去了简单工厂中的if else判断,解决了简单工厂违背开闭原则的诟病。下面我们还是举出一个实例来说明。

实例一,JDBC API

废话不多我们直接看类图 jdbc-api 我们可以看到,除了多了一个DriverManager其他于我们工厂方法的类图一模一样。Driver就是抽象工厂,而我们的数据库厂商会提供各自的驱动实现,Connection就是我们的抽象产品,同样数据库厂商会为我们提供各自的实现类,例如mysql的实现类com.mysql.jdbc.ConnectionImpl,oracle也会提供对应的实现类。下面看一看Driver接口部分源码:

public interface Driver {
    Connection connect(String url, java.util.Properties info)
        throws SQLException;
}

这个方法connect就是创建连接的方法定义,具体的实现都由数据库厂商实现好了,我们不用关心。接着在看一看Connection接口:

public interface Connection  extends Wrapper, AutoCloseable {
    Statement createStatement() throws SQLException;

    PreparedStatement prepareStatement(String sql)
        throws SQLException;
}

同样声明了很多我们平时使用的方法(创建静态,动态块执行我们的sql),具体实现都由不同的厂商提供。那么问题来了,居然DriverConnection就能搞定为什么还要一个DriverManager呢? DriverManager为我们解决了驱动间切换的问题,我们平时使用的时候直接Class.forName("driverClass"),然后DriverManager.getConnection("url","name","pwd")就获取到了连接。 按理说我们应该new Driver().connect(),针对不同的数据库创建不同的驱动对象。但是DriverManager帮我们屏蔽了这些操作,我们来看看具体是怎么实现的吧。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            //注册驱动到DriverManager缓存
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

这里是mysql的驱动类,我们调用Class.forName()的时候就吧驱动注册到了DriverManager,再来看看DriverManager怎么获取链接的:

public static Connection getConnection(String url,
       String user, String password) throws SQLException {
       java.util.Properties info = new java.util.Properties();

       if (user != null) {
           info.put("user", user);
       }
       if (password != null) {
           info.put("password", password);
       }

       return (getConnection(url, info, Reflection.getCallerClass()));
}

这是我们最熟悉的方法,好像还没到关键点。继续往下,直接看最关键的地方

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException
//下面是这个方法的代码片段:
       for(DriverInfo aDriver : registeredDrivers) {
           // 如果类加载器没法加载当前这个驱动,跳过继续往下。 从上面我们看到这个加载器是调用我们DriverManager的类的。
          if(isDriverAllowed(aDriver.driver, callerCL)) {
               try {
                   println("    trying " + aDriver.driver.getClass().getName());
                   Connection con = aDriver.driver.connect(url, info);
                   if (con != null) {
                       println("getConnection returning " + aDriver.driver.getClass().getName());
                       return (con);
                   }
               } catch (SQLException ex) {
                   if (reason == null) {
                       reason = ex;
                   }
               }
           } else {
             println("    skipping: " + aDriver.getClass().getName());
           }
         }
//判断是否有权限加载驱动
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if (driver != null) {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

            result = (aClass == driver.getClass()) ? true : false;
        }

        return result;
}

到这里我们明白了为啥么需要DriverManager了。

总结

设计原则:遵循单一职责、依赖倒置、开闭原则
常用场景:一种场景是希望工厂与产品的种类对客户端保持透明,给客户端提供一致的操作,另外一种是不同的工厂和产品可以提供客户端不同的服务或功能
使用概率:60%
复杂度:中低
变化点:工厂与产品的种类
选择关键点:工厂类和产品类是否是同生同灭的关系
逆鳞:无
相关设计模式
抽象工厂模式:工厂方法模式与抽象工厂模式最大的区别在于,在工厂方法模式中,工厂创造的是一个产品,而在抽象工厂模式中,工厂创造的是一个产品族。

Search

    Post Directory