深入netty21-netty中的设计模式-模板方法、迭代器、适配器详解

模板方法模式

模板方法模式(Template Method Pattern)是一种行为设计模式,用于在方法中定义一个操作的程序骨架(即算法的框架),并将某些步骤的执行延迟到子类中。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。

模板方法模式的主要特点:

  1. 算法框架:在父类中定义算法的骨架,即算法的一系列步骤。

  2. 可扩展性:子类可以扩展或修改算法的特定步骤,而不需要改变算法的整体结构。

  3. 钩子方法:在模板方法中,可以定义钩子(hook)方法,子类可以选择性地覆盖这些方法以影响算法的执行。

  4. 不可改变性:模板方法通常被声明为 final,以防止子类改变算法的骨架。

模板方法模式的结构:

  1. AbstractClass(抽象类):定义了模板方法和基本方法(包括抽象方法和具体方法)。

  2. ConcreteClass(具体类):继承抽象类并实现所有的抽象方法。

  3. 钩子方法:可选的,可以在抽象类中定义钩子方法,子类可以覆盖这些方法以执行额外的操作。

模板方法模式的实现步骤:

  1. 定义抽象类,声明模板方法和抽象方法。

  2. 在抽象类中实现具体方法,这些方法执行算法的一部分步骤。

  3. 定义模板方法,调用抽象方法和具体方法,以实现算法的骨架。

  4. 创建具体类,继承抽象类并实现所有的抽象方法。

  5. 在客户端代码中,创建具体类的实例并调用模板方法。

模板方法模式的适用场景:

  • 当需要将算法的某些步骤延迟到子类中实现时。
  • 当需要通过子类来扩展或修改算法的行为时。

模板方法模式的缺点:

  • 可能导致类体系结构的扩展性降低,因为添加新的操作可能需要修改算法骨架。
  • 可能会增加代码的复杂性,因为需要理解模板方法和抽象方法之间的关系。

模板方法模式在实际应用中的例子:

  • 排序算法:定义一个排序模板方法,不同的排序算法(如快速排序、归并排序等)可以作为子类实现。
  • 游戏关卡:定义一个游戏关卡的模板方法,不同的关卡可以作为子类实现特定的游戏逻辑。

模板方法模式在 Netty 中的应用:

在 Netty 中,ServerBootstrapBootstrap 类的初始化(init)过程体现了模板方法模式的典型应用。这两个类分别用于客户端和服务器端的网络编程,它们通过模板方法定义了初始化过程中的一系列步骤。此外,Netty 中的 ChannelHandler 及其子类经常使用模板方法模式。例如,ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter 提供了一系列的钩子方法,允许用户覆盖这些方法来处理特定的事件。

模板方法模式在 ServerBootstrap 和 Bootstrap 中的应用:
  1. 定义模板方法:在 AbstractBootstrap 类(ServerBootstrapBootstrap 的共同父类)中定义了模板方法 init()。这个方法是 final 的,意味着子类不能覆盖它,但可以在里面调用抽象方法和具体方法。

  2. 抽象方法init() 方法中会调用一些抽象方法,这些方法必须在子类中实现。例如,initChannel()initOptions() 就是需要子类实现的方法,用于分别配置通道(Channel)和设置参数。

  3. 具体方法:除了抽象方法,init() 还可能调用一些具体方法,这些方法是已经实现的,用于执行一些标准操作。

  4. 子类实现ServerBootstrapBootstrap 作为子类,需要实现由 AbstractBootstrap 中定义的抽象方法。

  5. 客户端和服务器端的初始化:在客户端和服务器端的初始化过程中,通过调用 init() 方法来执行一系列标准化的初始化步骤。

Bootstrap示例:
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
    final void init(C channel) {
        // 模板方法:初始化过程中的一系列步骤
        initChannel(channel);  // 抽象方法,需要子类实现
        initOptions(channel);   // 具体方法,设置通道的参数
    }

    protected abstract void initChannel(C channel) throws Exception;

    protected void initOptions(C channel) {
        // 设置一些默认的参数
    }
    
    // 省略其他代码
}

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
    @Override
    protected void initChannel(ServerChannel channel) throws Exception {
        // 服务器端特有的初始化逻辑
    }

    @Override
    protected void initOptions(ServerChannel channel) {
        // 设置服务器端的特定参数
    }
    
    // 省略其他代码
}

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        // 客户端特有的初始化逻辑
    }

    @Override
    protected void initOptions(Channel channel) {
        // 设置客户端的特定参数
    }
    
    // 省略其他代码
}

在 Netty 中,ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter 是两个抽象类,它们提供了处理入站和出站事件的基础结构。这两个类使用了模板方法模式,定义了一系列事件处理的钩子(hook)方法,允许用户通过继承并覆盖这些方法来插入自定义的事件处理逻辑。

ChannelInboundHandlerAdapter 的模板方法:

ChannelInboundHandlerAdapter 是用于处理入站事件的基类。它提供了以下模板方法:

  • channelRegistered(ChannelHandlerContext ctx):当 Channel 注册到 EventLoop 时调用。
  • channelUnregistered(ChannelHandlerContext ctx):当 Channel 从 EventLoop 注销时调用。
  • channelActive(ChannelHandlerContext ctx):当 Channel 变得活跃(即连接建立)时调用。
  • channelInactive(ChannelHandlerContext ctx):当 Channel 不再活跃(即连接关闭)时调用。
  • channelRead(ChannelHandlerContext ctx, Object msg):当数据被读取并接收时调用。
  • channelReadComplete(ChannelHandlerContext ctx):在调用 channelRead 之后,通知读取操作完成。
  • userEventTriggered(ChannelHandlerContext ctx, Object evt):当自定义的事件被触发时调用。
  • channelWritabilityChanged(ChannelHandlerContext ctx):当 Channel 的可写状态发生变化时调用。
  • exceptionCaught(ChannelHandlerContext ctx, Throwable cause):当操作中出现异常时调用。
ChannelOutboundHandlerAdapter 的模板方法:

ChannelOutboundHandlerAdapter 是用于处理出站事件的基类。它提供了以下模板方法:

  • bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise):用于绑定到特定的本地地址。
  • connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise):用于连接到远程地址。
  • disconnect(ChannelHandlerContext ctx, ChannelPromise promise):用于断开连接。
  • close(ChannelHandlerContext ctx, ChannelPromise promise):用于关闭 Channel
  • write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise):用于写数据到 Channel
  • flush(ChannelHandlerContext ctx):用于刷新 Channel,即将所有排队的消息发送出去。
HandlerAdapter示例代码:
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 自定义读取数据的逻辑
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 自定义异常处理的逻辑
        ctx.close();
    }
}

public class MyClientHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        // 自定义连接逻辑
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        // 自定义写数据的逻辑
    }
}

在这个示例中,MyServerHandler 继承自 ChannelInboundHandlerAdapter,覆盖了 channelReadexceptionCaught 方法。MyClientHandler 继承自 ChannelOutboundHandlerAdapter,覆盖了 connectwrite 方法。通过这种方式,用户可以在不实现所有方法的情况下,只关注和实现他们需要的事件处理逻辑。

模板方法模式的优势:

  • 标准化流程:模板方法定义了一个固定的算法结构,确保所有子类都遵循这一结构。
  • 扩展性:子类可以扩展或修改算法的特定步骤,而不需要改变算法的整体结构。
  • 减少重复代码:通过将通用代码放在模板方法中,减少了代码的重复。

迭代器模式

迭代器模式(Iterator Pattern)是一种对象行为设计模式,它允许一个用户访问一个容器对象中的元素而无需暴露该对象的内部表示。迭代器模式提供了一种通过统一的接口来遍历不同容器对象的方法。

迭代器模式的主要特点:

  1. 抽象访问:提供了一种抽象的方式来访问容器中的元素,不暴露容器的实现细节。

  2. 支持多种遍历方式:可以统一方式遍历不同的集合结构。

  3. 解耦容器对象和遍历对象:容器对象的修改不会影响到迭代器,反之亦然。

  4. 迭代器独立性:迭代器可以在不同的环境中使用,不依赖于具体的容器。

迭代器模式的结构:

  1. Iterator(迭代器接口):定义了遍历元素的方法,如 hasNext()next()remove()

  2. ConcreteIterator(具体迭代器):实现了迭代器接口,记录遍历过程中的状态。

  3. Aggregate(聚合接口):定义了创建具体迭代器的方法,如 createIterator()

  4. ConcreteAggregate(具体聚合):实现了聚合接口,返回一个与迭代器协调的迭代器实例。

迭代器模式的实现步骤:

  1. 定义迭代器接口,声明遍历元素的方法。

  2. 创建具体迭代器类,实现迭代器接口。

  3. 定义聚合接口,声明创建迭代器的方法。

  4. 创建具体聚合类,实现聚合接口,并提供返回具体迭代器实例的方法。

  5. 在客户端代码中,使用具体聚合对象的 createIterator() 方法来获取迭代器,并使用迭代器来遍历元素。

迭代器模式的适用场景:

  • 当需要访问一个容器对象的内容,而又不暴露其内部结构时。
  • 当需要提供多种方式遍历容器中的元素时。

迭代器模式的缺点:

  • 对于简单的遍历操作,迭代器模式可能会增加系统的复杂性。
  • 每次遍历都需要创建一个迭代器对象,可能会增加额外的资源消耗。

迭代器模式在实际应用中的例子:

  • Java Collections Framework:Java 的集合框架广泛使用了迭代器模式,如 ListIteratorIterator 等。

迭代器模式在 Netty 中的应用:

在 Netty 中,CompositeByteBuf 是一个实现了 ByteBuf 接口的类,它使用组合的方式来管理多个 ByteBuf 对象。CompositeByteBuf 本身并不是迭代器模式的一部分,但它在内部使用了一个迭代器来遍历和管理组成它的多个 ByteBuf 实例。

CompositeByteBuf 的特点:
  1. 组合多个 ByteBufCompositeByteBuf 可以包含多个 ByteBuf 对象,这些对象可以是任何实现了 ByteBuf 接口的实例。

  2. 视图整合:对于外部使用者来说,CompositeByteBuf 表现得就像一个单一的 ByteBuf,尽管它内部是由多个部分组成的。

  3. 动态调整:可以动态地向 CompositeByteBuf 中添加或删除 ByteBuf,就像在一个大的缓冲区中操作一样。

CompositeByteBuf 的结构:
  • Component ByteBufsCompositeByteBuf 内部维护了一个 ByteBuf 列表,这些是组成它的实际字节缓冲区。

  • Iterator:虽然 CompositeByteBuf 没有直接公开一个迭代器,但它内部使用了迭代器模式的概念来遍历这些组件 ByteBuf

CompositeByteBuf 的使用场景:
  • 内存管理:当需要将多个小的 ByteBuf 合并为一个大的缓冲区以优化内存使用时。

  • 协议处理:在处理网络协议时,可能会从多个地方接收数据,CompositeByteBuf 可以将这些数据合并为一个连续的缓冲区以简化处理。

CompositeByteBuf迭代器示例:
public class CompositeByteBufExample {
    public static void main(String[] args) {
        // 创建一个 CompositeByteBuf
        CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();

        // 创建一些 ByteBuf 实例并添加到 CompositeByteBuf 中
        ByteBuf buf1 = Unpooled.buffer().writeBytes(new byte[]{1, 2, 3});
        ByteBuf buf2 = Unpooled.buffer().writeBytes(new byte[]{4, 5, 6});
        compositeByteBuf.addComponent(true, buf1);
        compositeByteBuf.addComponent(true, buf2);

        // 使用一个循环来遍历 CompositeByteBuf 的组件
        Iterator<ByteBuf> iterator = compositeByteBuf.iterator();
        while (iterator.hasNext()) {
            ByteBuf component = iterator.next();
            // 处理每个组件 ByteBuf
            // 例如,打印组件内容
            System.out.println(component.toString());
        }

        // 释放 CompositeByteBuf 及其组件
        compositeByteBuf.release();
    }
}

在这个示例中,我们创建了一个 CompositeByteBuf 并添加了两个 ByteBuf 实例。addComponent() 方法用于添加新的 ByteBuf,并且可以指定是否需要在删除 CompositeByteBuf 时自动释放这些组件。

迭代器模式优点

迭代器模式(Iterator Pattern)提供了一种遍历集合元素的方法,而不需要暴露集合的底层实现。以下是迭代器模式的一些优点:

  1. 抽象化访问:迭代器模式通过提供一个统一的接口来访问集合中的元素,这使得客户端代码能够独立于集合的具体类。

  2. 支持多种遍历方式:不同的集合可以使用相同的迭代器接口来遍历,这使得对客户端来说,遍历列表和遍历树形结构的操作可以是一致的。

  3. 解耦数据结构与算法:迭代器模式将访问数据的算法从数据结构中分离出来,使得可以在不修改数据结构的前提下,开发新的遍历算法。

  4. 简化遍历操作:客户端代码不需要知道如何遍历集合,只需要使用迭代器提供的方法即可,简化了遍历操作。

  5. 提供灵活的遍历控制:迭代器模式允许在遍历过程中动态地添加、删除元素,而不需要重新编写遍历逻辑。

  6. 支持多重遍历:可以在不同的时间点开始或结束遍历,或者同时进行多个遍历,而不会相互干扰。

  7. 提高安全性:迭代器模式可以保护集合不被外部代码随意修改,例如,可以禁止在遍历过程中直接通过索引访问元素。

  8. 易于扩展:添加新的集合类型或遍历算法时,不需要修改现有的代码,只需要实现迭代器接口即可。

  9. 支持复合对象:可以对单个对象和对象组进行一致的遍历,迭代器模式可以处理单个对象,也可以处理由多个对象组成的聚合对象。

  10. 减少系统的复杂性:迭代器模式隐藏了集合的具体实现细节,客户端不需要了解集合内部的复杂结构。

  11. 增强代码的可读性:使用迭代器模式可以使代码更加清晰和易于理解,因为迭代器的使用方法通常是标准化的。

  12. 有助于实现懒加载:迭代器可以在遍历过程中延迟加载数据,这在处理大数据集或远程数据源时非常有用。

适配器模式

适配器模式(Adapter Pattern)是一种结构设计模式,它允许不兼容的接口之间的通信,将一个类的接口转换成客户端所期望的另一个接口。适配器模式常用于实现不同系统或组件之间的兼容。

适配器模式的主要特点:

  1. 接口转换:适配器模式提供一个一致的接口,用于访问不同或不兼容的接口。

  2. 解耦:适配器模式解耦了调用者和被调用者,使得它们不需要直接通信。

  3. 扩展性:可以在不修改原有对象的基础上,通过添加一个新的适配器来支持更多的具体类。

  4. 灵活性:在运行时可以选择使用哪个适配器,提供灵活性。

适配器模式的结构:

  1. Target(目标接口):定义客户端使用的特定领域相关的接口。

  2. Adaptee(被适配者):一个已经存在的类,需要适配,它可能有一个与目标接口不兼容的接口。

  3. Adapter(适配器):通过在内部包装一个 Adaptee 对象,把源接口转换成目标接口。

适配器模式的实现步骤:

  1. 定义目标接口,这是客户端所期望的接口。

  2. 识别被适配者的接口,这是需要适配的类。

  3. 创建适配器类,实现目标接口,并在内部包含一个被适配者的实例。

  4. 在适配器类中,实现目标接口的方法,并将调用转发给被适配者对象的相应方法。

  5. 在客户端代码中,使用适配器对象代替直接使用被适配者对象。

适配器模式的适用场景:

  • 当需要使用一个已有的类,但这个类的接口与期望的不兼容时。
  • 当希望创建一个可以复用的类,用于与一系列不兼容的接口协作时。

适配器模式的缺点:

  • 过度使用适配器模式可能会使系统变得复杂,难以维护。
  • 客户端代码依赖于适配器,如果适配器更改,可能需要修改客户端代码。

适配器模式在实际应用中的例子:

如在Spring中大量使用了适配器模式

  • Spring AOP 的 AdvisorAdapter :

    • Spring AOP 允许用户自定义切面(Advice)来增强方法的执行。不同的 Advice 类型(如 MethodBeforeAdviceAfterReturningAdvice 等)通过适配器模式适配成统一的 MethodInterceptor 接口,这样 Spring AOP 就可以统一处理这些不同的 Advice。
  • Spring MVC 的 HandlerAdapter

    • 在 Spring MVC 中,DispatcherServlet 负责前端控制器的分发。不同类型的 Controller(处理器)通过适配器模式适配到统一的 HandlerAdapter 接口,使得 DispatcherServlet 可以统一调用所有类型的 Controller 方法。
  • Spring JDBC 的 JdbcTemplate

    • JdbcTemplate 使用适配器模式来统一处理不同类型的 DataSource 和 JDBC 驱动,提供了一个统一的 API 来执行 JDBC 操作。
  • Spring 事务管理

    • Spring 事务管理器使用适配器模式来适配不同的事务策略,例如,基于 JTA 的全局事务管理器或特定于数据库的事务管理器。

适配器模式在 Netty 中的应用:

ScheduledFutureTask 是 Netty 中的一个类,它继承自 FutureTask 并实现了 RunnableScheduledFuture 接口。ScheduledFutureTask 通常用于在 EventExecutor(例如,Netty 的事件循环 EventLoop)中安排异步任务的执行。

ScheduledFutureTask 中,适配器模式的应用可能不是直接显而易见的,因为它更多地是关于任务调度和执行的。然而,如果我们考虑到它如何与 EventExecutor 交互,可以认为它在某种程度上实现了适配器模式的精神:

  1. 统一的接口ScheduledFutureTask 提供了一个统一的接口 RunnableScheduledFuture,该接口定义了任务执行和调度的方法,如 run()get()cancel() 等。

  2. 不同类型的任务:不同的任务可以通过 ScheduledFutureTask 来包装,从而适配到 Netty 的事件处理机制中。这意味着,尽管任务的具体实现可能各不相同,它们都可以适配到 EventExecutor 的调度模型中。

  3. 调度执行ScheduledFutureTask 将任务的执行和调度逻辑适配到 EventExecutor 的调度方法上,例如 schedule()scheduleAtFixedRate() 等。

  4. 灵活性:通过 ScheduledFutureTask,Netty 允许开发者以统一的方式安排任务,而不管这些任务的具体实现细节如何。这提供了高度的灵活性,并允许开发者专注于任务的业务逻辑。

下面是一个简化的示例,说明如何在 Netty 中使用 ScheduledFutureTask

import io.netty.util.concurrent.ScheduledFutureTask;
import io.netty.util.concurrent.EventExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class ScheduledTaskExample {
    public static void main(String[] args) {
        final EventExecutor executor = ...; // 获取 EventExecutor 实例

        // 创建一个 ScheduledFutureTask 实例,包装一个 Callable 任务
        ScheduledFutureTask<?> task = new ScheduledFutureTask<>(
                new Callable<Object>() {
                    @Override
                    public Object call() throws Exception {
                        // 执行任务的逻辑
                        return null;
                    }
                },
                null,
                System.nanoTime() + TimeUnit.SECONDS.toNanos(5) // 延迟5秒后执行
        );

        // 将任务安排到 EventExecutor 中执行
        executor.schedule(task, 5, TimeUnit.SECONDS);
    }
}

在这个示例中,ScheduledFutureTask 作为一个适配器,允许任何实现了 Callable 接口的任务被安排到 Netty 的 EventExecutor 中执行。这展示了适配器模式的精神,即不同的组件(这里是任务)可以通过一个统一的接口与一个外部系统(这里是 EventExecutor)交互。

ScheduledFutureTask 本身并不是一个适配器模式的典型例子,因为它不涉及将一个类的接口转换成客户希望的另一个接口。然而,它确实提供了一种机制,使得不同类型的任务能够适配到 Netty 的事件处理和调度模型中。

  • 45
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值