netty-设计模式小结

本文主要介绍了观察者、适配器和模板方法三种设计模式。观察者模式是被动等待通知,可通过异步检测状态实现;适配器模式利用复用父类方法,避免实现过多接口方法;模板方法模式将操作集中调用,注重回调操作本身。三种模式应用场景不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

观察者模式

开发中中会遇到如此的事情:每个模块都只完成自己的功能,但是只有全部整合才是我们的目标。

这是最常见不过的例子,每个部分都必须按照逻辑顺序进行执行,也可能是分支选择的执行。

写一起太长,所以有了封装的代码块,类或者方法。

但是在整合过程中,有不免各种的if...elseswitch...case

也就是说,我们需要知道每个部分执行到了什么哪个部分,到了没有,执行完没有,我们总想知道。

我们总想知道,在远方的亲人如何了,在干什么,生病没有。

就好比一个瘫痪的儿子:

  1. 醒了要吃,但是不会收拾
  2. 吃了要睡,但是不能洗碗
  3. 睡觉之前,不能自己洗脚

所以我们总需要尽心的去服侍,想更好的去了解他,去帮助他,要更好的去观察他。

面对你喜欢的人,不也是如此么,我们总知道什么场景该做啥,却不知道何时去做。

listener

public class PersonListener {
    private String name;
    public PersonListener(String name){
        this.name = name;
    }
    public void listenWakeUp(){
        show("make lunch");
    }
    public void listenGoSleep(){
        show("footbath");
    }
    public void listenEat(){
        show("heat up water");
    }
    public void show(Object msg){
        System.out.println(this.name + " : " + msg);
    }
}

person

public class Person {
    final public List<PersonListener> listeners = new ArrayList();
    public void addListenr(PersonListener listener){
        this.listeners.add(listener);
    }
    public void weakUp(){
        show("person weak up");
        listeners.forEach(listener -> listener.listenWakeUp());
    }
    public void goBead(){
        listeners.forEach(listener -> listener.listenGoSleep());
        show("person go to bed");
    }
    public void eat(){
        show("person eating");
        listeners.forEach(listener -> listener.listenEat());
    }
    public void show(Object msg) {
        System.out.println(msg);
    }
    public static void main(String[] args) {
        Person person = new Person();
        person.addListenr(new PersonListener("godme"));
        person.weakUp();
        person.eat();
        person.goBead();
    }
}

就是这个样子,不过可能和大家想象的背道而驰

image
truth
notify
observe
observer
actor
observer
actor

观感上是完全的背离,并不是主动的去观察,而是被动的等通知

毕竟自己的事情自己最清楚,外部进行观察总是比自己进行通知更麻烦。

不过这毕竟是代码中,而且是自己管控的代码,如果是追求别人,只能主动了,入侵别人电脑,也只能自己扫描。

netty中的观察者

public interface ChannelFuture extends Future<Void> {
    Channel channel();
    @Override
    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
    @Override
    ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
    @Override
    ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
    @Override
    ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
    @Override
    ChannelFuture sync() throws InterruptedException;
    @Override
    ChannelFuture syncUninterruptibly();
    @Override
    ChannelFuture await() throws InterruptedException;
    @Override
    ChannelFuture awaitUninterruptibly();
    boolean isVoid();
}

看看也能明白,它会在指定的时刻回调触发,好像我们在监听一般。

Reactor中的观察者

        while(true){
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            selectionKeys.forEach(selectionKey -> {
                try{
                    if(selectionKey.isAcceptable()){
                        ...
                    }else if(selectionKey.isReadable()){
                        ...
                       }
                    }
                }catch (Exception e){
                   e.printStackTrace();
                }
            });
            selectionKeys.clear();
        }

NIOselect不陌生吧,我们何时知道它执行差不多了呢,也是因为通知啊。

自己仿照一个

state

public enum State {
    CONNECT,READ,WRITE
}

Channel

public class MyChannel {
    private String name;
    public State state;
    public State listenState;
    public MyChannel(String name){
        this.name = name;
        this.state = State.READ;
    }
    static void sleep(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void connect(){
        new Thread(()->{
            MyChannel.sleep();
            state = State.CONNECT;
            read();
        }).start();

    }
    public void read(){
        new Thread(()->{
            MyChannel.sleep();
            state = com.godme.listener.State.READ;
            write();
        }).start();

    }
    public void write(){
        new Thread(()->{
            MyChannel.sleep();
            state = com.godme.listener.State.WRITE;
        } ).start();

    }
    public void showState(){
        System.out.println("channel["+this.name+"] is " + state + "ED");
    }
}

为了模拟出状态的慢慢执行,必须开启线程进行异步通知。

否则的话,在单个线程当中是顺序阻塞的,状态跳转过程不会被其他线程观察到,必须异步。

Selector

public class Selector {
    private final HashMap<State, HashSet<MyChannel>> stateGroup = new HashMap<>();
    private final HashSet<MyChannel> triggerSet = new HashSet<>();
    private static boolean trigger = false;
    {
        stateGroup.put(State.CONNECT, new HashSet<>());
        stateGroup.put(State.READ, new HashSet<>());
        stateGroup.put(State.WRITE, new HashSet<>());
        new ListenThread().start();
    }
    public void register(State state, MyChannel channel){
        channel.listenState  = state;
        stateGroup.get(channel.state).add(channel);
    }

    public HashSet<MyChannel> select() {
        trigger = false;
        while(!trigger){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return triggerSet;
    }
    class ListenThread extends Thread{
        public ListenThread(){
            this.setDaemon(false);
        }
        boolean notify = false;
        @Override
        public void run() {
            while(true){
                stateGroup.forEach((state, stateSet)->{
                    stateSet.forEach(channel -> {
                       if(channel.state != state){
                           stateSet.remove(channel);
                           stateGroup.get(channel.state).add(channel);
                       }
                    });
                });
                notify = false;
                stateGroup.forEach((state, stateSet)->{
                    stateSet.forEach(channel->{
                        if(channel.state == channel.listenState){
                            triggerSet.add(channel);
                            stateGroup.get(channel.state).remove(channel);
                            notify = true;
                        }
                    });
                });
                if(notify){
                    trigger = true;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这里也是异步的去检测状态,当状态和监听状态一致时,发出通知。

main

public class Main {
    public static void main(String[] args) {
        Selector selector = new Selector();
        MyChannel channel = new MyChannel("godme");
        selector.register(State.READ, channel);
        channel.connect();
        while(true){
            HashSet<MyChannel> selectionKeys = selector.select();
            selectionKeys.forEach(chanel->{
                if(channel.state == State.CONNECT){
                    chanel.showState();
                    selector.register(State.READ, channel);
                }else if(channel.state == State.READ){
                    chanel.showState();
                    selector.register(State.WRITE, channel);
                }else if(channel.state == State.WRITE){
                    chanel.showState();
                }
            });
            selectionKeys.clear();
        }
    }
}

仔细一看,是不是就很想NIO的那一部分呢。

不过这只是理解所谓事件通知,也就是观察者模式。

sync

channelFuture.channel().closeFuture().sync();

还记得sync么,可以参考一下select

    public HashSet<MyChannel> select() {
        trigger = false;
        while(!trigger){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return triggerSet;
    }

其实就是一直阻塞,等到通知以后再进行返回。

直接操作Future,有可能资源并没有准备好,会报错。

但是当Future没准备好之前,sync一直这样阻塞,准备好之后,发出通知。

所以,sync获取的资源必定是已经就绪的(如果不出其他故障的话)。

观察者,就是事件通知(调用)

适配器器模式

ActionInterface

public interface ActionInterface {
    public void hello();
    public void play();
    public void fuck();
}

ActionAdapeter

public class ActionAdapeter implements ActionInterface {
    @Override
    public void hello() {
    }
    @Override
    public void play() {
    }
    @Override
    public void fuck() {
    }
}

Action

public class Action extends ActionAdapeter {
    @Override
    public void play() {
        System.out.println("play");
    }
}

如你所见,适配器模式在于只做想做的

就好比一个Listener,我们定义的接口可以监听两百万个事件,我们怎么传入这么一个监听器呢?

全部实现不仅傻,而且稍微的变动就又得再写其他一百九十九万个方法?

利用继承,复用父类中的方法,这样,我们甚至直接传一个匿名接口,只需要实现自己需要的监听即可。

一般来说,使用Adapter来实现默认操作,这个操作可能是空操作。

然后子类就可以复用父类的实现,针对性的去进行单方法的复写(实现)。

层级结构而言,也是对接口的实现,可以泛型接收。

但是需要注意两点

  1. 声明的Adapter为了避免直接实例化,滥用产生歧义,最好是抽象类abstract
  2. 接口中的特异化方法,Adapter最好不要实现,保留抽象,让子类进行操作填充。

netty中的适配器

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelRegistered();
    }
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelUnregistered();
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelInactive();
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ctx.fireChannelRead(msg);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        ctx.fireUserEventTriggered(evt);
    }
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelWritabilityChanged();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.fireExceptionCaught(cause);
    }
}

这么多方法,实现到死啊,非得用适配器不可。

为什么?因为我懒。

模板方法

public class Main {
    public static void main(String[] args) {
        System.out.println("before");
        doSomething();
        System.out.println("after");
    }
    public static void doSomething(){
        System.out.println("doSomething");
    }
}

其实就是这个样子,把操作收归于一处,然后进行调用。

但是具体动作是反向调用,逆向填充

Parent

public abstract class Parent {
    abstract  public void hello();
    public void doSomething(){
        System.out.println("see somebody");
        hello();
        System.out.println("do something");
    }
}

Child

public class Child extends Parent{
    @Override
    public void hello() {
        System.out.println("你才可爱,你们全家都可爱");
    }
}

main

public class Main {
    public static void main(String[] args) {
        new Child().doSomething();
    }
}

看起来和观察者模式一样,都是关键时刻回调的样子。

的确,差别不大,或者说都是回调,但是,作用场景完全不同

  1. 观察者的回调在于时机,是发生什么的时候的附加处理,像try...catch的样子
  2. 模板方法的回调注重的就是回调操作本身,周边都是为他而服务

不仅侧重不一样,而且,顺序也不一样:

  1. 观察者是向下回调
  2. 模板方法是向上填充回调

更常见的例子就是数据库连接一样

public void deal(){
    Connect connect = Connect.open();
    deal(connect);
    connect.close();
}
abstract public void deal(Connect connect);

如果这个样子,我们以后处理连接问题,就完全不用去考虑关闭,也不用考虑连接获取问题。

把重心放在具体业务,一心一意做业务。

当然,如果用观察者模式的话,就是做完业务以后发通知进行回调了,但是不显得太麻烦了么?重点搞错了吧。

观察者模式用于不太紧密的的操作之间,属于平级的调用。

模板方法用于比较紧密的操作的流程制定,把基础准备和收尾工作做好,留下核心操作待填充。

netty中的模板方法

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }
        }
    }

看到ChannelRead0了么,这就是SimpleChannelInboundHandler中的使用了。

不仅泛型给我们直接转化过了,而且还自动给我们回收ReferenceCountUtil.release(msg)哦。

读取的RecvBuffer经过handler处理以后,实际上仍然在内存当中。

正如我们确定好不需要的对象需要person = null来通知释放,给JVM一个明确的标记。

ReferenceCountUtil.release(msg);就是这么一个明确标记,帮我们递减计数哦。

小结

目前已经出现了如下设计模式

  • 装饰器(IO)
  • Reactor
  • 观察者
  • 适配器
  • 模板方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值