将 defmulti 转换为 defprotocol

Converting defmulti to defprotocol

是否可以转换以下代码,使其使用 defprotocoldefrecord 而不是 defmultidefmethod

(defmulti test-multimethod (fn [keyword] keyword))

(defmethod test-multimethod :foo [a-map]
  "foo-method was called")

(defmethod test-multimethod :bar [a-map]
  "bar-method was called")

(defmulti perimeter (fn [shape] (:shape-name shape)))
(defmethod perimeter :circle [circle]
  (* 2 Math/PI (:radius circle)))
(defmethod perimeter :rectangle [rectangle]
  (+ (* 2 (:width rectangle)) (* 2 (:height rectangle))))

(def some-shapes [{:shape-name :circle :radius 4}
                   {:shape-name :rectangle :width 2 :height 2}])

(defmulti area (fn [shape] (:shape-name shape)))
(defmethod area :circle [circle]
  (* Math/PI (:radius circle) (:radius circle)))
(defmethod area :rectangle [rectangle]
  (* (:width rectangle) (:height rectangle)))

(defmethod perimeter :square [square]
  (* 4 (:side square)))
(defmethod area :square [square]
  (* (:side square) (:side square)))

(def more-shapes (conj some-shapes
                       {:shape-name :square :side 4}))


(for [shape more-shapes] (perimeter shape))
(for [shape more-shapes] (area shape))

是的,您在协议定义中声明了您的功能Shape,然后您在各种记录实现中定义了您的实现SquareCircle,等等

(defprotocol Shape 
  (area [this])
  (perimeter [this]))

(defrecord Square [side] Shape
  (area [this] (* (:side this) (:side this)))
  (perimeter [this] (* 4 (:side this))))

(defrecord Rect [w l] Shape
  (area [this] (* (:l this) (:w this)))
  (perimeter [this] (+ (:l this) (:l this) (:w this) (:w this))))

(def s (->Square 4))
(def r (->Rect 2 5))

(map area [s r]) ; '(16 10)
(map :side [s r]) ; '(4 nil)
(map :l [s r]) ; '(nil 5)

本质上这就像 OOP(但不可变),如果您熟悉的话。

尽管如此,关于 defmulti 实现的一个好处是,您通常可以序列化和反序列化您的地图并按原样使用它们,而不必将它们具体化到特定记录中 class。