netabare

netabare

V2EX 第 125600 号会员,加入于 2015-07-05 07:57:57 +08:00
为什么几乎所有观察者模式的实现代码都是用副作用实现的?
  •  1   
    程序员  •  netabare  •  13 小时 0 分钟前  •  最后回复来自 netabare
    21
    发现自己的 commit 标题和内容越来越长了
    程序员  •  netabare  •  167 天前  •  最后回复来自 netabare
    31
    这年头,参与开源项目还要付费才能参加了吗
    程序员  •  netabare  •  261 天前  •  最后回复来自 netabare
    50
    用 SwiftUI 做了个简易的找工作记录用的 App
  •  1   
    分享创造  •  netabare  •  2023-12-23 09:05:16 AM  •  最后回复来自 sherlockwoo
    2
    疑似发现了 Swift 的 NavigationStack 和 NavigationPath 的 bug?
    iOS  •  netabare  •  2023-12-12 01:50:10 AM  •  最后回复来自 alexcding
    5
    netabare 最近回复了
    @zhuisui
    @mahaoqu

    感谢讲解!

    所以这里我想我可能陷入了一个很大的误区,就是把 visitor 机械地等同于 pattern matching ,然后把 pattern matching 在 subtyping 下面的作用(也就是 dynamic dispatch )给搬运到了 visitor pattern 下,但是这个 dynamic dispatch 只是 visitor 的其中一个「稍微显著但不是主要的作用」,而 visitor pattern 的主要作用,就像 @mahaoqu 说的,「面向对象把一个类的多个方法放在一起,而 Visitor 模式恰好反过来了」,或者 @zhuisui 所说的「多个 visitor 分别代表可以输出不同形式的业务逻辑,visitor 之间是互相独立的」这样的作用。

    我的理解是你说的「避免了业务侧用 switch case 做模式匹配,仅需 iterate elements 的 accept 方法即可完成调用」用我前面的表达其实就是「 dynamic dispatch 」对吧。比如用户解析 Tree 的时候,它不关心 Tree 具体长啥样或者具体怎么解析,它只希望能够正确的拿到 XML/JSON 或者 Table 。那么 visitor pattern 就充当了这个解析的作用吧。

    这么说来倒是很多东西都说得清了。

    至于副作用的问题,主要是我对这样的代码有很大的意见:

    ```java
    class ... {
    /* L.18 */ResultType result;

    public void visitSomeArm(...) {

    /* L.259 */ ResultType oldResult = result;

    /* L.343 */ ... = visitAnotherArm(...);

    /* L.569 */ result = ...;
    }

    public void VisitAnotherArm(...) {
    /* L.1982 */ result = ...
    }
    }
    ```

    当然这个其实并不是 effectful visitor 的问题而更像是某种 structural 的 code smell 了,如果一个 visitor 的实现能够把副作用清晰的表达出来,让我能够人肉去建模出 Effect 大概长啥样,我对这样的代码并没有太大的意见。

    顺祝新年快乐!
    2 天前
    回复了 kran 创建的主题 Java 你喜欢使用 Java 下的哪个 web 框架?
    不喜欢,但如果我自己选我不会选 Java 。非要用的话可能会考虑 Javalin 、vertx 或者 Ktor 吧。
    5 天前
    回复了 freesun165 创建的主题 git 求助 git 自动 merge 丢代码
    所以不要把 master 合入分支,分支上面只用 rebase 。
    另外关于返回值,我给出的`ISomeVisitor`本身是用 T 去参数化限定它的返回类型。但是在业务代码里面返回类型受限应该不是什么问题?考虑一个复杂数据类型树的话,那么用泛型和返回值可以让 visitor 的职责更清晰吧?

    比如说一个订单和用户还有交易构成的数据类型的话,定义三个 visitor ,比如说

    ```java
    class OrderVisitor extends ISomeVisitor<IOrder>
    class TxVisitor extends ISomeVisitor<ITx>
    class UserVisitor extends ISomeVisitor<IUser>
    ```

    然后自然而然每个 visitor 的 visit 和 accept 分支都会遵守「这个 visitor 只处理和基类相关的职责」,最后再把 visitor 互相组合起来,不会比一个巨型的,什么都可以做的 visitor 更加清晰可维护吗?

    A little Java, A few Patterns 里面也有提到 visitor 之间可以组合嵌套的例子。

    我在实际代码里面就遇到过那种「一个巨型 visitor 同时承担很多职责」的例子,比如一个 ColorScheme 相关的 visitor 里面不光处理颜色,还要处理字体、排版,甚至把 cache 相关的函数都塞进去的情况。我不觉得哪怕在业务工程的考虑上这样的代码也比使用返回值和泛型参数的代码更好维护。
    @mahaoqu 话说 Expression Problem 有什么具体的文章或者讨论可以指一下路吗?我想看看。

    @lesismal 我倒是对设计模式并没有怎么太去学,但是造自己语言思考语义和语法的时候,再结合平时的日常工作,就不免会回过头想这些问题。

    @zhuisui 换句话说就是 visitor 从设计模式的角度讲只追求分离,并不在乎 dynamic dispatch 和子类型的运行时自动求解,对吗?如果从这个角度讲,那么似乎说得过去……但这么一来这个设计模式对我的兴趣可能也就剩不下多少了。

    @GiantHard 我觉得你说的有道理。感谢。
    电报我就觉得更不用提了,随意撤回这个功能甚至都让电报成为了电诈活动的温床了。你要说小圈子群聊有氛围,那你找到了正确的群,Q 群也可以「讨论氛围很学术很严谨」。
    reddit 是怎么个讨论法,版主买个 automod 把贴子秒删还是一 moderate 就 moderate 到天长地久,还是看版面管不住急眼了就跑出来给每个活跃用户都发个 permban ?

    karma 这个也属于经典「本意是好的,但执行坏了」。在 hackernews 和 stackoverflow 搞得好好的,跑 reddit 就成了 mod 随意乱删帖+karma farm 大灌水的双板斧。

    我可不觉得这有多严肃。
    @sagaxu 没必要抠字眼,我人在国外平时用的都是 visitor ,没那么熟悉中文定义。再说我这篇贴子里有提到一个 observer ?
    @w568w
    @nightwitch


    我能想到的一个「针对依赖访问状态」的解决办法就是加一个中间层来表达顺序访问或者访问次数等信息,最简单的实现可以是这样的:

    ```java
    interface IResult<T> { ... }
    class Skip<T> implements IResult<T> { ... }
    class Ok<T> implements IResult<T> { ... }
    ```

    这只是个例子,不代表说一定要这么做。当然实际业务代码几乎没人这么做就是,但我想说的是这个「依赖访问状态」并不是无解的。

    而且换过来说,业务代码里增加不同的层级和抽象不也是很经常的手法嘛? Stream 或者第三方库里面这样的工具也很多,为什么在 visitor 模式上反而不能够通过增加一层来解决问题了呢?引入可变状态和副作用的代价就不需要考虑一下吗?

    而且很多场景,例如我自己参与维护的一些代码项目里,经常在重构的时候需要花大量时间理解修改状态和副作用到底怎么桥接起来,最后发现很多代码实际上压根不依赖于访问状态,也对访问顺序没有任何前提要求,看起来更像是单纯的惯性所致,而且这样的 visitor 见一两个也就算了,项目里到处都是而且平均 LOC 几百上千行的时候,连重构都难下手。这也是我提出这个帖子的问题的原因——以前我是默认「 visitor 模式需要有状态,只是我不懂,可能工程代码里有最佳实践」,但在工作一两年接触了更多工程代码后,现在我对这个定论更多持有怀疑态度。

    反过来说,以教育和入门为目的的代码样例,例如大学 OOP 课上的代码,也是出于「 visitor 需要依赖可变状态」的前提来设计代码实现的吗?

    这里的问题在于,副作用、修改全局变量这些本质上都是不可推理并且无法用类型等东西来建模的,固然可以用单元测试来「告诉使用者这个 API 怎么使用」,但作为一个同时维护和使用 API 的开发者,我对这样的代码有极大的不适感。
    @donaldturinglee 我了解这个网站,而且这个网站也属于我提到的「用副作用实现观察者模式」的例子。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2504 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 02:10 · PVG 10:10 · LAX 18:10 · JFK 21:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.