工厂模式
工厂模式是一种常用的设计模式,它主要用于对象的创建。它的核心思想是通过定义一个接口或抽象类来规定一个对象的创建过程,而具体的创建工作由子类完成。这样,工厂模式可以将对象的创建和使用分离,使得系统更加灵活和易于扩展。下面详细解释一下这三种工厂模式:
简单工厂模式(Simple Factory Pattern)
简单工厂模式是通过一个工厂类来实现对象的创建。这个工厂类有一个静态方法,根据传入的参数来决定创建并返回哪种类型的对象实例。这种模式非常适合对象类型较少且创建逻辑相对简单的情况。
优点:
- 封装性:隐藏了对象创建的具体细节。
- 易于扩展:增加对象类型时,只需要修改工厂类的决策逻辑。
缺点:
- 违反开放封闭原则:每增加一个产品类,都需要修改工厂类的决策逻辑。
- 职责过重:工厂类承担了过多的职责,一旦产品类增多,工厂类会变得臃肿。
工厂方法模式(Factory Method Pattern)
工厂方法模式是简单工厂模式的改进。它定义了一个用于创建对象的接口或抽象类,让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类中进行。
优点:
- 遵循开放封闭原则:增加新的产品类时,不需要修改已有代码,只需增加相应的具体工厂类。
- 封装性:代码模块化,更易于管理和维护。
缺点:
- 系统的复杂性增加:每增加一个产品类,都需要增加一个具体的工厂类。
- 增加系统的抽象性:需要理解工厂方法和抽象产品的概念。
抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式是工厂方法模式的进一步扩展。它提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。抽象工厂模式适用于对象族的创建,即多个产品系列。
优点:
- 易于扩展:增加一个新的产品系列时,不需要修改已有代码,只需增加一个新的具体工厂。
- 隔离复杂对象的创建:客户端不需要知道具体的创建细节。
缺点:
- 增加新的产品对象时,需要修改所有的工厂类,这可能导致牵一发而动全身的问题。
- 类的个数急剧增加:每个具体类都需要一个具体的工厂类。
netty的工厂模式示例
Netty 的 ReflectiveChannelFactory
类使用了工厂方法模式来创建不同类型的 Channel
。这种设计允许 Netty 在运行时动态地创建服务端或客户端的 Channel
实例,而不需要在编译时确定具体的 Channel
类型。这是通过反射机制实现的,具体来说:
-
构造函数注入:
ReflectiveChannelFactory
接受一个Class
类型的参数,这个参数指定了要创建的Channel
的类型。 -
反射获取构造函数:在构造
ReflectiveChannelFactory
实例时,使用Class.getConstructor()
方法获取一个无参构造函数。这个构造函数将用于创建Channel
实例。 -
创建 Channel 实例:
newChannel()
方法使用反射调用之前获取的构造函数,来创建并返回一个新的Channel
实例。 -
类型安全:由于
ReflectiveChannelFactory
是泛型类,它确保了创建的Channel
实例是指定泛型类型T
的子类。
blic class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(ReflectiveChannelFactory.class) +
'(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)";
}
}
优点:
- 灵活性:通过反射,Netty 可以在运行时根据需要创建不同类型的
Channel
,这增加了框架的灵活性。 - 减少工厂类的数量:不需要为每种
Channel
类型创建一个单独的工厂类,从而减少了代码的冗余。
缺点:
- 性能开销:反射通常比直接的
new
操作要慢,因为它涉及到动态类型解析和方法调用。 - 安全性:反射可以破坏封装性,因为它允许访问私有构造函数和其他私有成员。
- 异常处理:使用反射时,需要处理
NoSuchMethodException
和其他反射相关的异常。
责任链模式
责任链模式(Chain of Responsibility Pattern)是一种对象行为设计模式,其核心思想是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
责任链模式的主要特点:
-
解耦请求者和接收者:请求的发送者不需要知道是哪个对象来处理这个请求,只需要知道链的存在。
-
动态链:链中的对象可以动态地添加或删除,不需要修改已有代码。
-
简单对象:链中的对象可以简单化,因为每个对象只负责它关心的请求。
-
避免多重条件判断:请求者不需要包含复杂的逻辑判断,这些逻辑可以分散到链中的各个对象中。
责任链模式的结构:
-
Handler(处理者):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个方法用于设置下一个处理者。
-
ConcreteHandler(具体处理者):实现处理者接口的具体类,包含具体的处理逻辑,如果能够处理请求,则处理请求;否则,将请求传递给链中的下一个处理者。
-
Client(客户端):创建具体的处理者对象,并构建责任链。
责任链模式的实现步骤:
-
定义请求处理接口,声明处理请求的方法和设置后继处理者的方法。
-
创建具体处理者类,实现请求处理接口。
-
在客户端代码中,根据需要创建具体处理者对象,并设置它们的后继处理者,从而构建起一条责任链。
-
发送请求,将请求传递给链的第一个处理者。
责任链模式的适用场景:
- 当多个对象都有可能处理同一个请求时,责任链模式可以避免请求发送者和接收者之间的耦合。
- 当需要动态指定处理请求的对象时,责任链模式可以在运行时根据需要动态地添加或删除处理者。
责任链模式的缺点:
- 可能导致请求处理的顺序不明确,特别是当链中处理者的数量很多时。
- 责任链可能会变得非常长,这可能导致性能问题。
- 调试不方便,因为请求在链中如何传递可能不容易追踪。
责任链模式在 Netty 中的应用:
Netty 中的 ChannelPipeline
和 ChannelHandler
就是责任链模式的一个典型应用。ChannelPipeline
维护了一系列的 ChannelHandler
,每个 ChannelHandler
都可以对网络事件进行处理。事件在 ChannelPipeline
中按照链式的方式传递,直到被某个 ChannelHandler
处理为止。这种方式使得网络编程更加模块化和灵活。
Netty 中的责任链模式实现主要体现在 ChannelPipeline
和 ChannelHandler
的设计上。下面是责任链模式在 Netty 中的具体实现细节:
责任处理器接口
在 Netty 中,ChannelHandler
接口扮演了责任处理器接口的角色。ChannelHandler
有两个重要的子接口:
ChannelInboundHandler
:用于拦截入站事件(如数据被接收、新的连接建立等)。ChannelOutboundHandler
:用于拦截出站事件(如数据被发送、连接关闭请求等)。
动态创建责任链
ChannelPipeline
是一个持有多个 ChannelHandler
的容器,它负责创建责任链。ChannelPipeline
的内部使用双向链表来维护 ChannelHandler
的顺序,可以动态地添加和删除处理器。
public class DefaultChannelPipeline implements ChannelPipeline {
static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);
private static final String HEAD_NAME = generateName0(HeadContext.class);
private static final String TAIL_NAME = generateName0(TailContext.class);
// 省略其他代码
final AbstractChannelHandlerContext head; // 头结点
final AbstractChannelHandlerContext tail; // 尾节点
private final Channel channel;
private final ChannelFuture succeededFuture;
private final VoidChannelPromise voidPromise;
private final boolean touch = ResourceLeakDetector.isEnabled();
// 省略其他代码
}
上下文
ChannelHandlerContext
是 ChannelHandler
的上下文信息,每个 ChannelHandler
都有一个对应的 ChannelHandlerContext
。ChannelHandlerContext
负责保存处理器的状态,并且提供了方法来访问 Channel
、ChannelPipeline
以及其他 ChannelHandler
。
责任传播和终止机制
ChannelHandlerContext
提供了一系列 fireXXX
方法,用于将事件传播给链中的下一个处理器。例如:
fireChannelRead(Object msg)
:当新的数据被读取时,调用这个方法将事件传递给下一个入站处理器。fireChannelReadComplete()
:表示当前所有的数据已经被读取,可以触发某些操作,如批量读取。fireExceptionCaught(Throwable cause)
:当发生异常时,调用这个方法将异常传递给异常处理器。
如果开发者在自定义的 ChannelHandler
中重写了某个事件处理方法,并且没有调用对应的 fireXXX
方法,那么事件的传播就会在该处理器处终止。
示例
以 ChannelInboundHandlerAdapter
的 channelRead
方法为例,当数据被读取时,Netty 会调用这个方法。如果开发者重写了这个方法,并且没有调用 ctx.fireChannelRead(msg)
,那么数据就不会继续沿着责任链传递给下一个入站处理器。
观察者模式
观察者模式(Observer Pattern),又称为发布-订阅模式(Publish-Subscribe Pattern),是一种对象行为设计模式。此模式定义了对象之间的一种一对多的依赖关系,当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。
观察者模式的主要特点:
-
对象间的抽象耦合:观察者模式允许对象间的耦合关系是抽象的,而不是具体的。一个对象(称为“主题”或“被观察者”)只知道它有一个或多个依赖者(称为“观察者”),但不知道这些依赖者的具体类。
-
动态添加或删除观察者:主题可以在运行时动态地添加或删除观察者。
-
广播通信:当主题的状态发生变化时,它会广播通知给所有注册的观察者,而不需要知道观察者的具体细节。
观察者模式的结构:
-
Subject(主题):也称为“Observable”,它维护了一系列观察者,并提供接口用于注册、注销和通知观察者。
-
Observer(观察者):定义了一个更新接口,使得在主题状态变化时,可以得到通知。
-
ConcreteSubject(具体主题):实现 Subject 接口的具体类,包含状态信息,当状态变化时,它会通知所有观察者。
-
ConcreteObserver(具体观察者):实现 Observer 接口的具体类,负责接收主题的更新通知,并执行相应的操作。
观察者模式的实现步骤:
-
定义 Subject 接口,声明注册、注销和通知观察者的方法。
-
创建具体主题类,实现 Subject 接口。
-
定义 Observer 接口,声明更新方法。
-
创建具体观察者类,实现 Observer 接口。
-
在客户端代码中,创建具体主题和具体观察者对象,并注册观察者到主题。
-
当主题的状态发生变化时,调用通知方法,主题会遍历所有注册的观察者并调用它们的更新方法。
观察者模式的适用场景:
- 当一个对象的改变需要同时改变其他对象时,且这些对象可能不止一个。
- 当对象间的耦合关系是动态的,且需要在运行时建立和解除。
观察者模式的缺点:
- 当观察者对象很多时,通知的分发可能会成为性能瓶颈。
- 如果主题的状态变化频繁,观察者模式可能导致大量的更新通知,从而影响性能。
- 观察者模式可能引起循环调用,如果主题的状态变化依赖于观察者的状态。
观察者模式在实际应用中的例子:
- 事件监听器:如用户界面编程中,按钮点击事件可以看作是主题状态的变化,而事件监听器就是观察者。
- 模型-视图-控制器(MVC)框架:在 MVC 中,视图可以注册为模型的观察者,当模型数据变化时,视图会自动更新。
- 软件系统的插件机制:插件可以作为观察者,当宿主软件的状态变化时,插件会得到通知并执行相应的操作。
观察者模式是一种非常实用的设计模式,它在软件开发中有着广泛的应用,特别是在需要实现对象间低耦合通信的场景中。
观察者模式在 Netty中的应用
观察者模式在 Netty 框架中得到了广泛的应用,主要用于处理异步事件和通知机制。Netty 的网络操作是异步的,这意味着在执行操作时,比如写操作或连接操作,并不会立即返回操作结果,而是通过回调的方式在操作完成时通知调用者。以下是观察者模式在 Netty 中的一些具体应用:
-
ChannelFuture 和 ChannelPromise:Netty 中的
ChannelFuture
表示一个可能会完成的异步操作,而ChannelPromise
是ChannelFuture
的一个变体,允许用户对异步操作进行定制。它们都是基于观察者模式实现的,允许用户添加监听器(观察者)来响应操作完成事件。 -
FutureListener:
FutureListener
是一个接口,用于定义当ChannelFuture
完成时执行的操作。当网络操作完成时,Netty 会自动调用这些监听器,从而实现异步通知。 -
异步操作:Netty 的
writeAndFlush()
方法是观察者模式的一个典型应用。当一个 IO 操作开始时,会创建一个ChannelFuture
对象,用户可以通过addListener()
方法添加监听器来响应操作完成。 -
事件通知:Netty 通过
notifyListeners()
和operationComplete()
方法实现通知观察者的功能,完成异步流程。 -
连接监听:在处理网络连接时,Netty 使用观察者模式来监听连接完成事件。例如,
ChannelPromise
可以用来设置连接操作完成时的回调。 -
资源利用和性能优化:通过使用观察者模式,Netty 可以充分利用线程池资源,避免因等待网络操作完成而启动额外的线程,从而提高性能和资源利用率。
-
解耦操作和响应:观察者模式使得 Netty 的操作(如写操作、读操作)与响应(即操作完成后的回调)之间解耦,提高了代码的模块化和可维护性。
-
状态变化通知:当主题对象(如
Channel
)的状态发生变化时,Netty 会通知所有注册的观察者(如ChannelFutureListener
),使它们能够自动更新自己。
ChannelFuture详解
在 Netty 中,ChannelFuture
和 ChannelFutureListener
是观察者模式的典型实现。ChannelFuture
代表了一个可能在未来某个时间点会完成的操作,而 ChannelFutureListener
则是对该操作完成时的响应定义。
以下是 ChannelFuture
和 addListener
方法在观察者模式中的运用:
ChannelFuture
ChannelFuture
是 Netty 中用于表示异步操作结果的对象。它可以告诉你操作是否成功完成、是否发生了异常或者是否被取消。ChannelFuture
本身提供了多种方法来检查操作的状态,例如:
isDone()
:检查操作是否完成。isSuccess()
:检查操作是否成功完成。cause()
:如果操作失败,返回导致失败的异常。
ChannelFutureListener
ChannelFutureListener
是一个接口,定义了当 ChannelFuture
完成时需要执行的操作。它有一个方法 operationComplete(ChannelFuture future)
,当 ChannelFuture
完成时,这个方法会被调用。
addListener 方法
addListener
方法允许你向 ChannelFuture
添加一个 ChannelFutureListener
。这样,当异步操作完成时,Netty 会自动调用所有的 ChannelFutureListener
,执行你定义的逻辑。
ChannelFuture 示例
ChannelFuture channelFuture = channel.writeAndFlush(object);
channelFuture.addListener(future -> {
if (future.isSuccess()) {
// do something
} else {
// do something
}
});
channel.writeAndFlush(object)
:这是一个异步操作,它会立即返回一个ChannelFuture
对象,而不会等待操作实际完成。addListener
:这个方法接受一个ChannelFutureListener
作为参数。在这个例子中,使用了 Lambda 表达式来创建一个匿名的ChannelFutureListener
。future.isSuccess()
:在ChannelFutureListener
的实现中,通过检查ChannelFuture
的状态来决定执行哪个分支的代码。