协议与记录 #

一、协议简介 #

协议(Protocol)是Clojure的高性能多态机制,类似于接口但更灵活。协议支持对现有类型进行扩展。

1.1 协议vs多重方法 #

特性 协议 多重方法
派发方式 类型 任意函数
性能
扩展性 可扩展现有类型 灵活
适用场景 类型多态 复杂派发

1.2 定义协议 #

clojure
(defprotocol Drawable
  (draw [this])
  (area [this]))

(defprotocol Named
  (name [this])
  (full-name [this]))

二、记录(Record) #

2.1 定义记录 #

clojure
(defrecord Circle [radius])

(def circle (->Circle 5))

(:radius circle)

(.radius circle)

2.2 实现协议 #

clojure
(defrecord Circle [radius]
  Drawable
  (draw [this]
    (str "Drawing circle with radius " radius))
  (area [this]
    (* Math/PI radius radius)))

(def c (->Circle 10))

(draw c)

(area c)

2.3 创建记录实例 #

clojure
(def c1 (->Circle 5))

(def c2 (Circle. 5))

(def c3 (map->Circle {:radius 5}))

(into {} c1)

2.4 多协议实现 #

clojure
(defrecord Rectangle [width height]
  Drawable
  (draw [this]
    (str "Drawing rectangle " width "x" height))
  (area [this]
    (* width height))
  Named
  (name [this]
    "rectangle")
  (full-name [this]
    (str width "x" height " rectangle")))

(def r (->Rectangle 10 5))

(draw r)

(area r)

(name r)

三、扩展协议 #

3.1 extend-type #

为现有类型扩展协议:

clojure
(extend-type String
  Named
  (name [this]
    this)
  (full-name [this]
    (str "String: " this)))

(name "hello")

(full-name "hello")

3.2 extend-protocol #

一次为多个类型扩展协议:

clojure
(extend-protocol Named
  String
  (name [this] this)
  (full-name [this] (str "String: " this))
  Number
  (name [this] (str "Number: " this))
  (full-name [this] (str "Number: " this)))

(name "test")

(name 42)

3.3 扩展Java类型 #

clojure
(extend-type java.util.Collection
  Drawable
  (draw [this]
    (str "Collection with " (count this) " items")))

(draw [1 2 3])

四、类型(deftype) #

4.1 定义类型 #

clojure
(deftype Point [x y])

(def p (->Point 10 20))

(.x p)

(.y p)

4.2 type vs record #

特性 deftype defrecord
字段访问 直接 通过关键字
Map接口 不支持 支持
元数据 不支持 支持
性能 更高
用途 底层实现 数据载体

4.3 实现协议 #

clojure
(deftype Vector2D [x y]
  Drawable
  (draw [this]
    (str "Vector(" x ", " y ")"))
  (area [this]
    (* x y)))

(def v (->Vector2D 3 4))

(draw v)

(area v)

4.4 可变字段 #

clojure
(deftype Counter [^:volatile-mutable count]
  Drawable
  (draw [this]
    (str "Counter: " count))
  (area [this]
    (set! count (inc count))
    count))

(def counter (->Counter 0))

(draw counter)

(area counter)

(area counter)

五、协议函数 #

5.1 可选参数 #

clojure
(defprotocol Formatter
  (format [this] [this options]))

(defrecord SimpleFormatter [data]
  Formatter
  (format [this]
    (format this {}))
  (format [this options]
    (str data " with " options)))

(def f (->SimpleFormatter "data"))

(format f)

(format f {:style :pretty})

5.2 协议继承 #

clojure
(defprotocol Shape
  (perimeter [this]))

(defrecord Triangle [a b c]
  Shape
  (perimeter [this]
    (+ a b c)))

(def t (->Triangle 3 4 5))

(perimeter t)

六、实践示例 #

6.1 图形系统 #

clojure
(defprotocol Shape
  (area [this])
  (perimeter [this])
  (scale [this factor]))

(defrecord Circle [radius]
  Shape
  (area [this]
    (* Math/PI radius radius))
  (perimeter [this]
    (* 2 Math/PI radius))
  (scale [this factor]
    (->Circle (* radius factor))))

(defrecord Rectangle [width height]
  Shape
  (area [this]
    (* width height))
  (perimeter [this]
    (* 2 (+ width height)))
  (scale [this factor]
    (->Rectangle (* width factor) (* height factor))))

(def shapes [(->Circle 5) (->Rectangle 10 5)])

(map area shapes)

(map perimeter shapes)

6.2 数据库访问 #

clojure
(defprotocol Database
  (connect [this])
  (disconnect [this])
  (query [this sql]))

(defrecord PostgresDB [host port db-name]
  Database
  (connect [this]
    (str "Connecting to Postgres: " host ":" port "/" db-name))
  (disconnect [this]
    (str "Disconnecting from Postgres"))
  (query [this sql]
    (str "Executing: " sql)))

(defrecord MySQLDB [host port db-name]
  Database
  (connect [this]
    (str "Connecting to MySQL: " host ":" port "/" db-name))
  (disconnect [this]
    (str "Disconnecting from MySQL"))
  (query [this sql]
    (str "Executing: " sql)))

(defn use-database [db]
  (connect db)
  (query db "SELECT * FROM users")
  (disconnect db))

(use-database (->PostgresDB "localhost" 5432 "mydb"))

6.3 序列化系统 #

clojure
(defprotocol Serializable
  (serialize [this])
  (deserialize [this data]))

(defrecord JSONSerializer []
  Serializable
  (serialize [this]
    :json)
  (deserialize [this data]
    (str "JSON: " data)))

(defrecord XMLSerializer []
  Serializable
  (serialize [this]
    :xml)
  (deserialize [this data]
    (str "XML: " data)))

(defn process [serializer data]
  (deserialize serializer data))

(process (->JSONSerializer) {:a 1})

七、记录作为Map #

7.1 Map接口 #

clojure
(defrecord User [name email])

(def user (->User "Alice" "alice@example.com"))

(:name user)

(assoc user :age 30)

(dissoc user :email)

(contains? user :name)

(keys user)

(vals user)

7.2 转换 #

clojure
(def user (->User "Alice" "alice@example.com"))

(into {} user)

(-> user
    (assoc :age 30)
    (update :name str " Smith"))

八、性能考虑 #

8.1 协议调用性能 #

clojure
(defprotocol FastProtocol
  (fast-method [this]))

(defrecord FastRecord [data]
  FastProtocol
  (fast-method [this] data))

(defrecord SlowRecord [data])

(extend-type SlowRecord
  FastProtocol
  (fast-method [this] (:data this)))

(def fast (->FastRecord "test"))
(def slow (->SlowRecord "test"))

(time (dotimes [_ 1000000] (fast-method fast)))
(time (dotimes [_ 1000000] (fast-method slow)))

8.2 类型提示 #

clojure
(defn process-shape [^Shape shape]
  (area shape))

九、总结 #

协议与记录速查:

概念 用途
defprotocol 定义协议
defrecord 定义记录
deftype 定义类型
extend-type 扩展单个类型
extend-protocol 扩展多个类型

关键点:

  1. 协议提供高性能多态
  2. 记录是数据载体,支持Map接口
  3. 类型适合底层实现
  4. 可以扩展现有类型
  5. 记录可以像Map一样操作

下一步,让我们学习元数据!

最后更新:2026-03-27