定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
类图
优缺点
优点
- 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不需要知道对象是如何创建的,降低模块间的耦合;
- 良好的扩展性。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可完成系统扩展;
- 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。
工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,我不需要的就不去交流;也符合依赖倒置原则,只依赖产品类的抽象;当然也符合里氏替换原则,使用产品子类替换产品父类也没问题!
缺点
- 增加了系统的复杂度。在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 增加了系统的抽象性和理解难度。由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
应用
- 工厂方法模式是new一个对象的替代品。所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
- 需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。
例如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();
}
}