clojure 将协议(protocol)定义保存在与实现不同的命名空间中

标签 clojure namespaces protocols

我一直在尝试建立一门学科,将我的协议(protocol)定义分离到他们自己的命名空间中,主要是作为一种风格选择。我不喜欢这种方法的一件事是,由于命名空间需要的任何内容实际上对该命名空间都是“私有(private)的”,因此想要从另一个命名空间调用协议(protocol)函数的用户必须在他们的协议(protocol)代码中添加一个 require 语句。
例如:
协议(protocol)定义命名空间:

(ns project.protocols)

(defprotocol Greet
  (greet [this greeting]))
实现命名空间:
(ns project.entities
 (:require [project.protocols :as protocols]))

(defrecord TheDude
  [name drink]
  protocols/Greet
  (greet [this greeting]
    (println "The Dude sips a" drink)
    (println greeting)))
核心命名空间:
(ns project.core
 (:require [project.protocols :as protocols]
           [project.entities :refer [TheDude]]))

(let [dude (TheDude. "Jeff" "white russian")]
  (protocols/greet dude "not on the rug, man..."))
这工作得很好,但我并不特别喜欢用户需要意识到需要 project.protocols这实际上是 project.entities 内部的实现细节.在其他语言中,我会引用 project.entities/greetproject.core但是命名空间不会在 Clojure 中“导出”它们所需的变量,它们仅在需要的命名空间内部。我看到了两个明显的替代方案,第三个可能是使用 Potemkin 之类的东西:
  • 不要将协议(protocol)定义放在单独的命名空间中,只需将它们定义在与实现相同的文件中(例如 project.entities 此处)。
  • 在实现文件中,创建指向每个协议(protocol)函数的变量(这很丑陋,感觉不对,但有效)。

  • 以数字 2 为例:
    (ns project.entities
     (:require [project.protocols :as protocols]))
    
    (defrecord TheDude 
      [name drink]
      protocols/Greet
      (greet [this greeting]
        (println "not on the rug, man...")
        (println "guess i'll have another " drink)))
    
    (def greet protocols/greet) ; ¯\_(ツ)_/¯
    
    我的问题,我认为主要是偏好之一,是处理这种关注点分离的“最佳实践”(如果有的话)方法是什么?我意识到添加 requireproject.core只是多了一行,但我关心的不是行数,而是最小化用户需要注意的内容。

    编辑 :
    我认为实现这一点的显而易见的方法是不要期望用户需要两个命名空间,而是创建一个核心命名空间来为他们做这件事:
    (ns project.core
      (:require [project.protocols :as protocols]
                [project.entities :refer [TheDude]]))
    
    ;; create wrapper 'constructor' functions like this for each record in `project.entities`
    (defn new-dude 
      [{:keys [name drink] :as dude}]
      (map->TheDude dude))
    
    ;; similarly, wrap each protocol method 
    (defn greet [person phrase]
      (protocols/greet person phrase))
    
    现在任何用户都可以只需要核心,如果他们想将协议(protocol)扩展到不同命名空间中自己的记录,他们可以这样做并调用 core/greet将采用新的实现。此外,如果有任何前/后处理要完成,可以在“更高级别”的 API 函数中处理 core/greet .

    最佳答案

    在需要协议(protocol)的程序中,通常对象在某些地方被实例化,并在其他地方(通过协议(protocol))消费。也许在某些并非如此的情况下,您实际上并不需要协议(protocol)。实际上,“要求”协议(protocol)及其实现的命名空间并不常见。如果它经常发生,那就是代码异味。
    pete23 的回答提到使用点语法调用记录的方法而不涉及协议(protocol)的命名空间。但是使用协议(protocol)功能有一些适度的优势。
    如果没有实现继承,协议(protocol)仅包含基本(“原始”)功能。这样的功能实现起来方便,但不一定对调用者 super 友好。协议(protocol)命名空间是添加非原始访问器的好地方,您可能在面向对象中将其声明为接口(interface)上的默认方法或抽象基类上的继承非抽象方法。使用协议(protocol)命名空间的消费者可以调用原语和非原语。
    有时,原语需要所有实现通用的预处理或后处理。无需在每个实现中重复常见的东西!只需轻轻重构:从 f 重命名协议(protocol)函数至 -f ,更新实现,并添加一个函数 f在包装 -f 的协议(protocol)命名空间中与必要的前和后。调用者不需要任何改变。

    关于clojure 将协议(protocol)定义保存在与实现不同的命名空间中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68676623/

    相关文章:

    swift - init() 与 Swift 协议(protocol)有什么关系?

    Clojure 命名空间

    asynchronous - 当mapcat破坏core.async中的背压时,内存泄漏在哪里?

    php - 如何自动加载具有不同子命名空间的 Controller ?

    namespaces - 如何使用在头文件中声明和定义的命名空间?

    swift - Swift 中是否可以自定义合成条件一致性?

    ios - Objective-C 如何正确实现协议(protocol)或其他解决方案

    clojure - Clojure 中如何构造类型?

    windows - Clojure(需要'examples.introduction)返回FileNotFound异常

    ruby-on-rails - 如何防止 Ruby 以 gem 的类而不是我的类为目标?