多态与多重方法 #

一、多重方法简介 #

多重方法是Clojure实现运行时多态的机制。与面向对象语言的多态不同,Clojure的多重方法基于任意函数的返回值进行派发,更加灵活。

1.1 基本概念 #

clojure
(defmulti greet :type)

(defmethod greet :person [entity]
  (str "Hello, " (:name entity)))

(defmethod greet :robot [entity]
  (str "Beep boop, I am " (:name entity)))

(greet {:type :person :name "Alice"})

(greet {:type :robot :name "R2D2"})

1.2 组成部分 #

部分 说明
defmulti 定义多重方法,指定派发函数
defmethod 定义特定派发值的方法实现
派发函数 决定调用哪个方法的函数
派发值 派发函数返回的值,用于匹配方法

二、定义多重方法 #

2.1 defmulti #

clojure
(defmulti name dispatch-fn)

(defmulti area :shape)

(defmulti calculate (fn [x y] (class x)))

(defmulti process first)

2.2 defmethod #

clojure
(defmethod area :circle [shape]
  (* Math/PI (:radius shape) (:radius shape)))

(defmethod area :rectangle [shape]
  (* (:width shape) (:height shape)))

(area {:shape :circle :radius 5})

(area {:shape :rectangle :width 10 :height 5})

2.3 默认方法 #

clojure
(defmulti greet :type)

(defmethod greet :person [entity]
  (str "Hello, " (:name entity)))

(defmethod greet :default [entity]
  (str "Unknown entity: " entity))

(greet {:type :person :name "Alice"})

(greet {:type :alien :name "ET"})

三、派发函数 #

3.1 关键字派发 #

clojure
(defmulti draw :shape)

(defmethod draw :circle [shape]
  (str "Drawing circle with radius " (:radius shape)))

(defmethod draw :rectangle [shape]
  (str "Drawing rectangle " (:width shape) "x" (:height shape)))

3.2 函数派发 #

clojure
(defmulti process (fn [x] (class x)))

(defmethod process String [s]
  (str "String: " s))

(defmethod process Integer [n]
  (str "Integer: " n))

(defmethod process :default [x]
  (str "Unknown: " (class x)))

(process "hello")

(process 42)

(process 3.14)

3.3 多参数派发 #

clojure
(defmulti calculate (fn [a b] [(class a) (class b)]))

(defmethod calculate [String String] [a b]
  (str a b))

(defmethod calculate [Integer Integer] [a b]
  (+ a b))

(defmethod calculate [Integer String] [a b]
  (str a b))

(calculate "Hello " "World")

(calculate 1 2)

(calculate 1 " apple")

3.4 复杂派发 #

clojure
(defmulti classify
  (fn [item]
    (cond
      (and (map? item) (:error item)) :error
      (map? item) :success
      (coll? item) :collection
      :else :unknown)))

(defmethod classify :error [item]
  (str "Error: " (:message item)))

(defmethod classify :success [item]
  (str "Success: " (:data item)))

(defmethod classify :collection [item]
  (str "Collection with " (count item) " items"))

(classify {:error true :message "Failed"})

(classify {:data "result"})

(classify [1 2 3])

四、层次结构 #

4.1 derive #

clojure
(derive ::dog ::animal)
(derive ::cat ::animal)
(derive ::husky ::dog)

(isa? ::husky ::dog)

(isa? ::husky ::animal)

(isa? ::cat ::dog)

4.2 层次结构派发 #

clojure
(defmulti speak :type)

(defmethod speak ::animal [entity]
  (str (name (:type entity)) " makes a sound"))

(defmethod speak ::dog [entity]
  (str (name (:type entity)) " barks"))

(defmethod speak ::cat [entity]
  (str (name (:type entity)) " meows"))

(speak {:type ::dog :name "Buddy"})

(speak {:type ::husky :name "Max"})

(speak {:type ::cat :name "Whiskers"})

4.3 parents和ancestors #

clojure
(parents ::husky)

(ancestors ::husky)

(descendants ::animal)

4.4 make-hierarchy #

clojure
(def my-hierarchy (make-hierarchy))

(def my-hierarchy
  (-> my-hierarchy
      (derive :mammal :animal)
      (derive :dog :mammal)
      (derive :cat :mammal)))

(defmulti speak-h :type :hierarchy #'my-hierarchy)

(defmethod speak-h :animal [entity]
  "Some sound")

(defmethod speak-h :dog [entity]
  "Woof!")

五、方法组合 #

5.1 prefer-method #

当多个方法匹配时,指定优先级:

clojure
(defmulti process :type)

(defmethod process :animal [x]
  (str "Animal: " (:name x)))

(defmethod process :pet [x]
  (str "Pet: " (:name x)))

(prefer-method process :pet :animal)

(derive ::dog :animal)
(derive ::dog :pet)

(process {:type ::dog :name "Buddy"})

5.2 methods和get-method #

clojure
(defmulti greet :type)

(defmethod greet :person [x] "Hello person")
(defmethod greet :robot [x] "Beep boop")

(methods greet)

(get-method greet :person)

5.3 remove-method #

clojure
(defmulti test-multi :type)

(defmethod test-multi :a [x] "A")

(remove-method test-multi :a)

(get-method test-multi :a)

六、实践示例 #

6.1 序列化系统 #

clojure
(defmulti serialize :format)

(defmethod serialize :json [data]
  (str "{\"value\": " (:value data) "}"))

(defmethod serialize :xml [data]
  (str "<value>" (:value data) "</value>"))

(defmethod serialize :edn [data]
  (str {:value (:value data)}))

(serialize {:format :json :value 42})

(serialize {:format :xml :value 42})

(serialize {:format :edn :value 42})

6.2 图形渲染 #

clojure
(defmulti render :shape)

(defmethod render :circle [{:keys [radius center]}]
  (str "Circle at " center " with radius " radius))

(defmethod render :rectangle [{:keys [width height position]}]
  (str "Rectangle " width "x" height " at " position))

(defmethod render :triangle [{:keys [vertices]}]
  (str "Triangle with vertices " vertices))

(render {:shape :circle :radius 5 :center [0 0]})

(render {:shape :rectangle :width 10 :height 5 :position [1 1]})

6.3 支付处理 #

clojure
(defmulti process-payment :method)

(defmethod process-payment :credit-card [{:keys [card-number amount]}]
  {:status :success
   :message (str "Charged $" amount " to card ending in "
                 (subs card-number (- (count card-number) 4)))})

(defmethod process-payment :paypal [{:keys [email amount]}]
  {:status :success
   :message (str "Charged $" amount " via PayPal to " email)})

(defmethod process-payment :bank-transfer [{:keys [account amount]}]
  {:status :pending
   :message (str "Transfer of $" amount " initiated to account " account)})

(process-payment {:method :credit-card :card-number "1234567890123456" :amount 100})

(process-payment {:method :paypal :email "user@example.com" :amount 50})

6.4 数据验证 #

clojure
(defmulti validate :type)

(defmethod validate :string [{:keys [value min-len max-len]}]
  (cond
    (< (count value) (or min-len 0))
    {:valid false :error "Too short"}
    (> (count value) (or max-len Integer/MAX_VALUE))
    {:valid false :error "Too long"}
    :else
    {:valid true}))

(defmethod validate :number [{:keys [value min max]}]
  (cond
    (< value (or min Integer/MIN_VALUE))
    {:valid false :error "Too small"}
    (> value (or max Integer/MAX_VALUE))
    {:valid false :error "Too large"}
    :else
    {:valid true}))

(validate {:type :string :value "hello" :min-len 1 :max-len 10})

(validate {:type :number :value 5 :min 1 :max 10})

七、多重方法vs协议 #

7.1 对比 #

特性 多重方法 协议
派发方式 任意函数 类型
性能 较慢 较快
灵活性
适用场景 复杂派发逻辑 类型派发

7.2 选择建议 #

使用多重方法当

  • 需要复杂派发逻辑
  • 需要基于多个参数派发
  • 需要层次结构派发

使用协议当

  • 基于类型派发
  • 需要高性能
  • 面向对象风格

八、总结 #

多重方法要点:

概念 说明
defmulti 定义多重方法和派发函数
defmethod 定义方法实现
:default 默认方法
derive 建立层次关系
prefer-method 解决冲突

关键点:

  1. 多重方法实现运行时多态
  2. 派发函数决定调用哪个方法
  3. 层次结构支持继承式派发
  4. 比协议更灵活但性能稍低

Clojure函数系列完成!接下来学习控制流!

最后更新:2026-03-27