• 观察者模式与回调


    观察者模式与回调

    观察者模式又叫发布订阅模式,有订阅者和发布者;发布者可以包含了多个订阅者订阅的事件,一旦发布者执行,会执行所有的订阅者订阅的事件。我觉得这么讲还是很迷糊。其实就是说“发布者”是一段上层代码,他知道他所需要执行的过程中会发生一些事情,而这些事情具体逻辑自己又不知道,就算知道所有的逻辑,要用条件分支判断执行,这总归的是不好的,所以才有了这个模式。这是一个非常棒的模式。他使得发布者的代码保持不变。而订阅者的事件可以散步在他们自己的代码中。

    我们实际应用中最常见的就是页面中的按钮点击事件。当我们双击webform中的按钮后会自动生成一个btn_OnClick的方法,然后在里面编写一些逻辑,同时也生成了btn.Click+= new EventHandler(btn_OnClick)代码(只是2.0之后这个代码就被隐藏起来了),这就是给按钮btn(订阅者)订阅了一个事件。这些逻辑理当属于按钮所在的页面,而不是需要执行这个方法的代码中。

    当按钮点击之后,会触发页面的提交,webform框架可以获取是哪个按钮被点击过,然后执行btn.Click(),就可以执行我们具体的逻辑了。 设想如果不用这个模式,按钮的Click方法是不是要写很多switch来判断是哪个按钮,然后调用该有的逻辑。

    那么假如说我们不用.NET的这套事件机制,该如何漂亮的抽象出Click的代码呢?

    其实只要相当上一节的策略模式,我们只要给Click接受一个IClickEvent接口,然后button类再包含一组IClickEvent的成员,就可以遍历这些成员执行了。订阅的代码就变成了button.AddEvent(new xxClickEvent());即可。

    在.net中,我们没必要用IClickEvent接口的形式,因为我们有委托这个方法代理(或者叫方法指针),他可以说是一个只具有一个方法的接口,而.net中的事件本身也是委托。只是事件形式的委托是封闭的,不可在外部直接赋值操作,只能订阅和删除订阅。

    综上来看,观察者模式不过是一个处理未知方法的模式,他漂亮的把具体逻辑分散到他该属于地方。

    在web前端的Javascript中,这种情况就更为普遍。比如我们用jQuery时,给一个按钮增加一个onClick方法,只需要$(“#btn”).click(function(){})即可。浏览器会知道具体的哪个按钮被点击,甚至我们随便点击页面的一个地方,都会被浏览器截获,假如我们有相应的OnClick方法,他会执行调用,并传值给我们当前鼠标的位置等。 在我们发起一个ajax请求时,会有一个参数是callback方法,在判断完XmlHttpRequest的readyState == 4后调用,每个ajax的请求完后的callback都不一致。所以说他也是观察者模式。我们说这种叫做回调模式是不是更好?所谓的“回”就是使用之前的代码,而不是当前的代码。

    所以说委托或者回调(方法指针)也是一种抽象,在不具备这种能力的语言里可用接口来代替。重复上节的话,抽象提取变化事物的共性,不管是面向对象还是过程式还是函数式,都离不开抽象的思想。

    说了这么多,不知道表达清楚没有,我们来讲一个实际应用不依赖于框架的。

    比如我们发布一篇文章,常用逻辑就是保存文章。如果哪天来了新的需求,比如说跟某某公司合作,发表完文章之后需要给用户增加一些奖励。又过了几天又来一个新的需求,所最近抓的紧,需要对文章审核,一旦有违禁的关键词不允许发布。这两个逻辑之前都不存在。我们是不是要修改代码呢?这两个需求都是临时的。假如修改的保存逻辑,回头合作取消和风头过后又要取消掉。这太不合适了。我们应让代码的修改的范围缩到最小。

    如果我们在保存逻辑前后增加事件,比如PreSave和EndSave,然后对应增加相关的订阅代码,就不需要修改Save的逻辑。但是调用Save的代码依然需要增加两个方法和订阅代码。

    public class ArticleController : Controller
    {
        public ActionResult Save(Article article)
        {
            var articleManager = new ArticleManager();
            articleManager.PreSaveEvent +=  x =>
            {
                var shouldBlock = BlockWords.Filter(article);
                if(shouldBlock)
                {
                    throw new SecretExcepetion("对不起,您的文章中包含违禁关键词,请检查");
                    //封杀用户
                }
            };
             
            articleManager.EndSaveEvent += x=>
            {
                //奖励用户
            }
             
            articleManager.Save(article);
        }
    }
     
    public class ArticleManager
    {
        public event Action<Article> PreSaveEvent;
        public event Action<Article> EndSaveEvent;
         
        public void Save(Article article)
        {
            if(PreSaveEvent!=null)
            {
                PreSaveEvent(article);
            }
            var db = new ArticleRepository();
            db.Save(article);
            if(EndSaveEvent!=null)
            {
                EndSaveEvent(article);
            }
        }
    }
     
    public class ArticleRepository
    {
        public void Save(Article article)
        {
            database.Save(article);
        }
    }

    虽然上面的用的是事件的方式,如果用委托作为Save的参数也可以,只是作为参数对以后的重构可能会带来麻烦。

    这样只需要修改Controller的代码就能改变这些需求,如果不连Controller的代码也不想改怎么办呢?后面再说吧。

  • 相关阅读:
    C++11常用特性的使用经验总结
    Websocket协议的学习、调研和实现
    高性能后台服务器架构设计
    Linux下ping命令、traceroute命令、tracert命令的使用
    docker-4-Dockerfile配置文件详解
    Docker 国内仓库和镜像
    centos7.0查看IP
    tomcat报错:This is very likely to create a memory leak问题解决
    windows 环境下搭建docker私有仓库
    Docker 修改镜像源地址
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3163995.html
Copyright © 2020-2023  润新知