1. 初衷
写这篇文章的初衷,介绍一下工厂模式,以及具体使用实例。
2. 简单介绍
首先明确一点就是工厂是用来干啥的,工厂是用来创建对象的。
工厂模式的作用
- 封装对象的创建过程: 工厂模式将对象的创建过程封装在工厂类中,使客户端代码不需要了解具体的构建过程,只需向工厂请求对象即可。
- 提高代码的可维护性: 工厂模式可以集中管理对象的创建,如果需要修改对象的构建方式,只需修改工厂类而不影响客户端代码。
- 提高代码的扩展性: 工厂模式允许添加新的产品类型而无需修改现有客户端代码。这样可以方便地扩展应用程序。
- 降低耦合度: 使用工厂模式可以降低客户端代码与具体产品类之间的耦合度,因为客户端只与工厂接口交互,不需要直接与产品类交互。
- 提高代码的重用性: 工厂模式可以在不同的地方使用相同的工厂来创建对象,提高了代码的重用性。
3. 简单工厂模式
3.1 请求快递实例,无设计模式情况
公共快递下单接口
public interface IExpressDelivery {
/**
* 快递下单
*/
OrderResponse createOrder(OrderRequest orderRequest);
}
京东快递下单
public class JDExpressDelivery implements IExpressDelivery{
@Override
public OrderResponse createOrder(OrderRequest orderRequest) {
// 省略请求京东的下单api过程
OrderResponse orderResponse = new OrderResponse();
orderResponse.setBillNo("京东快递单号");
return orderResponse;
}
}
顺丰快递下单
public class SFExpressDelivery implements IExpressDelivery {
@Override
public OrderResponse createOrder(OrderRequest orderRequest) {
// 省略请求顺丰的下单api过程
OrderResponse orderResponse = new OrderResponse();
orderResponse.setBillNo("顺丰快递单号");
return orderResponse;
}
}
圆通快递下单
public class YTExpressDelivery implements IExpressDelivery {
@Override
public OrderResponse createOrder(OrderRequest orderRequest) {
// 省略请求圆通的下单api过程
OrderResponse orderResponse = new OrderResponse();
orderResponse.setBillNo("圆通快递单号");
return orderResponse;
}
}
客户端调用
public class Test {
public static void main(String[] args) {
String type = "SF";
OrderResponse orderResponse = createOrderByType(type);
System.out.println("快递单号为"+orderResponse.getBillNo());
}
public static OrderResponse createOrderByType(String type) {
IExpressDelivery expressDelivery = null;
if (type.equals("JD")) {
expressDelivery = new JDExpressDelivery();
} else if (type.equals("SF")) {
expressDelivery = new SFExpressDelivery();
} else {
expressDelivery = new YTExpressDelivery();
}
OrderRequest orderRequest = new OrderRequest();
return expressDelivery.createOrder(orderRequest);
}
}
从客户端调用就可以看出客户端是需要依赖实现类的,当前举的例子实例化还是很简单的,如果实例化需要更复杂的代码客户端的代码也会看起来很臃肿。
3.2 请求快递实例,使用简单工厂模式
工厂
public class ExpressDeliveryFactory {
public static IExpressDelivery create(String deliveryType) {
IExpressDelivery expressDelivery = null;
if (deliveryType.equals("JD")) {
expressDelivery = new JDExpressDelivery();
} else if (deliveryType.equals("SF")) {
expressDelivery = new SFExpressDelivery();
} else {
expressDelivery = new YTExpressDelivery();
}
return expressDelivery;
}
}
客户端
public static void main(String[] args) {
String type = "SF";
OrderResponse orderResponse = createOrderByType(type);
System.out.println("快递单号为"+orderResponse.getBillNo());
}
public static OrderResponse createOrderByType(String type) {
IExpressDelivery expressDelivery = ExpressDeliveryFactory.create(type);
OrderRequest orderRequest = new OrderRequest();
return expressDelivery.createOrder(orderRequest);
}
3.3 简单工厂模式 + 反射优化
快递枚举类
@Getter
@AllArgsConstructor
public enum ExpressTypeEnum {
JD( "JD", "com.example.demo.express.JDExpressDelivery"),
SF("SF", "com.example.demo.express.SFExpressDelivery"),
YT("YT", "com.example.demo.express.YTExpressDelivery"),
;
private final String key;
private final String classPath;
}
优化后的工厂
public class ExpressDeliveryFactory {
public static IExpressDelivery create(ExpressTypeEnum expressTypeEnum) {
try {
Class<?> clazz = Class.forName(expressTypeEnum.getClassPath());
return (IExpressDelivery) clazz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
客户端
public class Test {
public static void main(String[] args) {
OrderResponse orderResponse = createOrderByType(ExpressTypeEnum.SF);
System.out.println("快递单号为"+orderResponse.getBillNo());
}
public static OrderResponse createOrderByType(ExpressTypeEnum type) {
IExpressDelivery expressDelivery = ExpressDeliveryFactory.create(type);
OrderRequest orderRequest = new OrderRequest();
return expressDelivery.createOrder(orderRequest);
}
}
3.4 简单工厂模式的主要优点:
- 将对象的创建和具体产品的实现分离,降低了客户端代码与具体产品类的耦合度。
- 简化了客户端代码,客户端不需要了解对象的创建细节,只需调用工厂类的方法即可获得所需的对象。
- 可以轻松切换或替换具体产品类,不影响客户端的代码。
3.5 简单工厂模式一些局限性:
- 当需要添加新的产品类型时,需要修改工厂类的代码,违反了开闭原则。
- 工厂类可能会变得相当庞大,难以维护。
4. 工厂方法模式
4.1 请求快递实例
将工厂创建产品的方法提出来创建一个抽象类,把原来的工厂变成抽象工厂
public interface IExpressFactory {
IExpressDelivery create();
}
创建多个工厂
京东快递的工厂
public class JDExpressFactory implements IExpressFactory{
@Override
public IExpressDelivery create() {
return new JDExpressDelivery();
}
}
顺丰快递的工厂
public class SFExpressFactory implements IExpressFactory {
@Override
public IExpressDelivery create() {
return new SFExpressDelivery();
}
}
圆通快递的工厂
public class YTExpressFactory implements IExpressFactory {
@Override
public IExpressDelivery create() {
return new YTExpressDelivery();
}
}
工厂类的简单工厂
先建立枚举类
@Getter
@AllArgsConstructor
public enum ExpressFactoryTypeEnum {
JD( "JD", "com.example.demo.express.JDExpressFactory"),
SF("SF", "com.example.demo.express.SFExpressFactory"),
YT("YT", "com.example.demo.express.YTExpressFactory"),
;
private final String key;
private final String classPath;
}
工厂的工厂
public class ExpressFactoryFactory {
public static IExpressFactory create(ExpressFactoryTypeEnum expressFactoryTypeEnum) {
try {
Class<?> clazz = Class.forName(expressFactoryTypeEnum.getClassPath());
return (IExpressFactory) clazz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
最后在客户端调用
public class Test1 {
public static void main(String[] args) {
OrderResponse orderResponse = createOrderByType(ExpressFactoryTypeEnum.JD);
System.out.println("快递单号为"+orderResponse.getBillNo());
}
private static OrderResponse createOrderByType(ExpressFactoryTypeEnum typeEnum) {
IExpressFactory iExpressFactory = ExpressFactoryFactory.create(typeEnum);
IExpressDelivery expressDelivery = iExpressFactory.create();
OrderRequest orderRequest = new OrderRequest();
return expressDelivery.createOrder(orderRequest);
}
}
4.2 工厂方法模式总结
工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。
4.3工厂方法模式的主要优点
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合"开闭原则"。
4.4 工厂方法模式的主要缺点
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
4.5 适用场景
- 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
- 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
4.6 具体应用 java.net 网络包中的工厂方法模式
4.6.1 工厂1 URLStreamHandlerFactory
URLStreamHandlerFactory是抽象工厂, 这个接口用于创建特定协议的 URLStreamHandler 实例,它通常在 Java 网络编程中用于处理不同网络协议的 URL。
具体来说,这个接口有以下重要方法和注释:
createURLStreamHandler(String protocol)方法:这个方法用于创建一个特定协议的URLStreamHandler实例。传入参数protocol表示协议的名称(如 "http"、"ftp" 等),该方法会返回与该协议相关的URLStreamHandler实例。
这个接口的典型用途是在 Java 中处理不同网络协议的 URL。不同的网络协议需要不同的处理器来打开、读取和写入 URL 内容。URLStreamHandlerFactory 允许根据不同的协议动态创建相应的处理器,从而实现多协议的 URL 处理。
/**
* This interface defines a factory for {@code URL} stream
* protocol handlers.
* <p>
* It is used by the {@code URL} class to create a
* {@code URLStreamHandler} for a specific protocol.
*
* @author Arthur van Hoff
* @see java.net.URL
* @see java.net.URLStreamHandler
* @since JDK1.0
*/
public interface URLStreamHandlerFactory {
/**
* Creates a new {@code URLStreamHandler} instance with the specified
* protocol.
*
* @param protocol the protocol ("{@code ftp}",
* "{@code http}", "{@code nntp}", etc.).
* @return a {@code URLStreamHandler} for the specific protocol.
* @see java.net.URLStreamHandler
*/
URLStreamHandler createURLStreamHandler(String protocol);
}
私有静态内部类 Factory是具体工厂 ,实现了 URLStreamHandlerFactory 接口。它用于动态创建特定协议的 URLStreamHandler 实例
private static class Factory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";
private Factory() {
}
public URLStreamHandler createURLStreamHandler(String var1) {
String var2 = PREFIX + "." + var1 + ".Handler";
try {
Class var3 = Class.forName(var2);
return (URLStreamHandler)var3.newInstance();
} catch (ReflectiveOperationException var4) {
throw new InternalError("could not load " + var1 + "system protocol handler", var4);
}
}
}
4.6.2 工厂2 URLStreamHandler
java.net.URLStreamHandler 是一个工厂类,通过 openConnection(java.net.URL) 方法来创建java.net.URLConnection 的实例。java.net.URLStreamHandler 实现灵活度很大,既可以通过不同 protocol 的URL 实例,产生 java.net.URLConnection 对象。还可以通过相同 protocol 的多个 URL 对象,来产生对象。通用性实现,一种协议对应一个 java.net.URLStreamHandler 实现。
/**
* The abstract class {@code URLStreamHandler} is the common
* superclass for all stream protocol handlers. A stream protocol
* handler knows how to make a connection for a particular protocol
* type, such as {@code http} or {@code https}.
* <p>
* In most cases, an instance of a {@code URLStreamHandler}
* subclass is not created directly by an application. Rather, the
* first time a protocol name is encountered when constructing a
* {@code URL}, the appropriate stream protocol handler is
* automatically loaded.
*
* @author James Gosling
* @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
* @since JDK1.0
*/
public abstract class URLStreamHandler {
/**
* Opens a connection to the object referenced by the
* {@code URL} argument.
* This method should be overridden by a subclass.
*
* <p>If for the handler's protocol (such as HTTP or JAR), there
* exists a public, specialized URLConnection subclass belonging
* to one of the following packages or one of their subpackages:
* java.lang, java.io, java.util, java.net, the connection
* returned will be of that subclass. For example, for HTTP an
* HttpURLConnection will be returned, and for JAR a
* JarURLConnection will be returned.
*
* @param u the URL that this connects to.
* @return a {@code URLConnection} object for the {@code URL}.
* @exception IOException if an I/O error occurs while opening the
* connection.
*/
abstract protected URLConnection openConnection(URL u) throws IOException;
// 还有很多方法没有贴出来
}
URLStreamHandler 的很多子类
其中一个子类
public class Handler extends sun.net.www.protocol.http.Handler {
protected String proxy;
protected int proxyPort;
protected int getDefaultPort() {
return 443;
}
public Handler() {
this.proxy = null;
this.proxyPort = -1;
}
public Handler(String var1, int var2) {
this.proxy = var1;
this.proxyPort = var2;
}
protected URLConnection openConnection(URL var1) throws IOException {
return this.openConnection(var1, (Proxy)null);
}
protected URLConnection openConnection(URL var1, Proxy var2) throws IOException {
return new HttpsURLConnectionImpl(var1, var2, this);
}
}
URLConnection介绍,URLConnection 是 Java 标准库中的一个类,用于在应用程序和网络资源之间建立连接,以便进行数据传输和通信。它是用于处理各种网络协议的通用接口,例如 HTTP、FTP、文件传输等。URLConnection 是 Java 网络编程的核心组件之一,提供了访问和与网络资源交互的方式。
以下是 URLConnection 的一些关键特点和功能:
- 多协议支持:
URLConnection支持多种网络协议,这使得它能够连接和交互不同类型的网络资源。每个协议都有对应的协议处理程序,如 HTTP 协议的处理程序是HttpURLConnection。 - 连接管理:
URLConnection处理了连接的管理细节,包括连接的建立、保持和关闭。它还支持连接池,以便在多次请求之间重用连接,提高性能。 - 读取和写入数据: 通过
getInputStream()方法,你可以从网络资源中读取数据,而通过getOutputStream()方法,你可以向资源写入数据。这使得可以通过网络传输数据,如下载文件或上传数据。 - 请求和响应属性: 你可以设置请求属性(如 User-Agent、Authorization)和获取响应属性(如响应码、响应消息)来定制和检查连接的行为。
- 代理支持:
URLConnection支持代理服务器,允许你通过代理访问互联网资源。 - Cookie 支持: 你可以管理和使用 cookies,以保持会话状态和授权。
- 重定向处理:
URLConnection能够自动处理重定向,从而在需要时能够访问其他URL。 - 缓存支持:
URLConnection支持缓存,可以减少对网络资源的重复请求。 - 异常处理:
URLConnection抛出异常来指示连接或通信错误,你可以捕获这些异常并采取相应的措施。
URLConnection 是 Java 网络编程的重要组成部分,它使 Java 应用程序能够轻松地与互联网上的资源进行通信,包括获取数据、发送数据、授权和访问不同类型的网络服务。它的多协议支持和灵活性使其在许多不同类型的应用中非常有用。