在Clojure中用什么来代替类和对象?你能提供一个例子吗?

时间:2012-06-24 20:13:05

标签: oop class clojure

你能举例说明一个人如何代表一个"玩家"具有可变属性的类,如HP和位置(3d矢量),init,setter和getter等函数?

3 个答案:

答案 0 :(得分:12)

在真正惯用的Clojure中,你的“玩家”将是不可变的,你可能会将其表示为地图,例如:

{:type        :player
 :team        :red
 :hit-points  10
 :location    [17 9 6]} 

播放器可能包含在更大的“世界”数据结构中,并且会有一个纯函数update-world可以创建一个具有任何必要修改的新世界(例如将玩家移动到新位置)

对于getter / setter - 只需使用法线贴图操作函数。在您操作标准Clojure数据的情况下,通常不需要getter / setter。

答案 1 :(得分:2)

您还可以定义记录,这些记录具有与地图类似的界面和语义,但提供了多种好处。例如,访问记录中的成员比在地图中更快。此外,您可以在记录上扩展协议,并将其用于记录上的快速多态分派。例如你可以在各种形状对象上扩展绘制协议。根据Rich Hickey(http://www.infoq.com/interviews/hickey-clojure-reader#),使用协议帮助他们使用Clojurescript编译器。

e.g。

(defrecord Action [time key args state] 
    (comment protocol extension can go here))

答案 2 :(得分:2)

我同意mikera的说法,你应该尝试不可变地做到这一点,但你要求专门针对可变属性,所以我的建议方式与mikera的答案非常相似,但要在地图中使用atoms你将拥有可能想要改变的可变属性。

(def player1
  {:type       :player
   :team       :red
   :hit-points (atom 10)
   :location   (atom [17 9 6])})

请注意,只有您可能想要更改的内容才会包含在原子中。为了访问可变数据,您必须取消引用它,如下所示:

@(player1 :hit-points)
10

要设置值,您可以使用swap!reset!,如下所示:

(swap! (player1 :hit-points) dec)
9
@(player1 :hit-points)
9
(reset! (player1 :hit-points) 2)
2
@(player1 :hit-points)
2

这可以作为制作一个玩家的一个例子,尽管你要求的东西有init,getter和setter。我应该说在这一点上我几乎没有Clojure以外的编程经验,所以我可能没有完全掌握那些将会是什么,但这就是我将如何设置它。

(defn new-player
  [hit-points location]
  {:type       :player
   :team       :red
   :hit-points (atom hit-points)
   :location   (atom location)})

然后,当我想制作新玩家时,我会这样做:

(def my-player
  (new-player 20 [0 0 0]))
{:type       :player
 :team       :red
 :hit-points #<Atom@1959415: 20>
 :location   #<Atom@12d0e49: [0 0 0]>}

我认为没有必要制作明确的“getter”和/或“setter”,因为您可以通过解除引用来获取任何可变数据,并使用swap!reset!设置任何可变数据与我上面展示的方式完全相同。话虽如此,如果你愿意,你可以这样做:

(defn get-hp
  [player]
  @(player :hit-points))
(defn get-loc
  [player]
  @(player :location))
(defn set-hp
  [player new-hp]
  (reset! (player :hit-points) new-hp))
(defn set-loc
  [player new-loc]
  (reset! (player :location) new-loc))

现在你可以这样做:

(get-hp my-player)
20
(get-loc my-player)
[0 0 0]
(set-hp my-player 17)
17
(get-hp my-player)
17
(set-loc my-player [0 1 1])
[0 1 1]
(get-loc my-player)
[0 1 1]

由于这个答案还不够长,我认为在制作新玩家时包含默认值可能会很好。我可以想到一个简单但不一定优雅的方法:

(defn new-player
  ([]
   (new-player 20 [0 0 0]))
  ([hp-or-loc]
    (cond
      (integer? hp-or-loc) (new-player hp-or-loc [0 0 0])
      (vector? hp-or-loc)  (new-player 20 hp-or-loc)
      :else                (throw (Exception. "Value must be either integer for hp or vector for location."))))
  ([hp loc]
   {:type       :player
    :team       :red
    :hit-points (atom hp)
    :location   (atom loc)}))

现在默认情况下,新玩家将拥有20马力并位于[0 0 0]位置。如果传递整数或向量,它将假定应该是hp或location(分别)的值,否则它将抛出异常。

同样,我认为在大多数情况下可变数据可能是不必要的,最简单的解决方案可能是将问题设想为“如何制作这种可变数据结构”,而不是“如何创建新的,更新版本的不可变数据,然后将这些数据传递回循环的开头,之后我可能会更新并再次重复“。

希望其中一些有用。