定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

类图

工厂模式类图
工厂模式类图

优缺点

优点

  1. 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不需要知道对象是如何创建的,降低模块间的耦合;
  2. 良好的扩展性。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可完成系统扩展;
  3. 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。

工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类也没问题!

缺点

  1. 增加了系统的复杂度。在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  2. 增加了系统的抽象性和理解难度。由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

应用

  1. 工厂方法模式是new一个对象的替代品。所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
  2. 需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。

例如JDBC数据库的连接有Mysql, Oracle, sqlserver 等方式这就是典型的工厂模式,又或者在设计连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,这种也可以使用工厂模式。

实现

在我们的一个定时任务项目中有这样的一种场景,A客户希望使用Token方式鉴权的HTTP请求来进行数据的接收,B客户希望使用AK/SK的方式,C客户希望使用Signature的方式,为了满足不同的客户的接收需求我们针对这个做了一个工厂类。

定义HttpClient接口用作推送:

public interface IHttpClient {

   void push();
}

不同方式的子类实现:

public class AkSkHttpClient implements IHttpClient {
   @Override
   public void push() {
      System.out.println("使用AKSK推送");
   }
}
public class SignatureHttpClient implements IHttpClient {
   @Override
   public void push() {
      System.out.println("使用SIGNATURE方式推送");
   }
}
public class TokenHttpClient implements IHttpClient {
   @Override
   public void push() {
      System.out.println("使用TOKEN方式推送");
   }
}

工厂类:

public class HttpClientFactory {

   public IHttpClient getPushHttpType(HttpClientType httpClientType) {
      switch (httpClientType) {
         case AKSK:
            return new AkSkHttpClient();
         case TOKEN:
            return new TokenHttpClient();
         case SIGNATURE:
            return new SignatureHttpClient();
         default:
            break;
      }
      return null;
   }

   public enum HttpClientType {
      TOKEN, AKSK, SIGNATURE;
   }
}

调用:

public class PushService {
   public static void main(String[] args) {
      HttpClientFactory httpClientFactory = new HttpClientFactory();
      IHttpClient httpClient = httpClientFactory.getPushHttpType(HttpClientFactory.HttpClientType.AKSK);
      if (httpClient == null) {
         System.out.println("异常!没有找到指定类型的HttpClient");
      }
      httpClient.push();
   }
}