3

您能否提供一个示例,说明如何表示具有可变属性(例如 HP 和 position(3d 矢量))的“播放器”类,以及诸如 init、setter 和 getter 之类的函数?

4

3 回答 3

12

在真正惯用的 Clojure 中,您的“播放器”将是不可变的,您可能会将其表示为地图,例如:

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

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

至于 getter / setter - 只需使用法线贴图操作功能。当您只是在操作标准 Clojure 数据时,通常不需要 getter/setter。

于 2012-06-24T20:35:07.913 回答
2

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

例如

(defrecord Action [time key args state] 
    (comment protocol extension can go here))
于 2012-06-24T21:26:35.407 回答
2

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

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

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

@(player1 :hit-points)
10

为了设置您可以使用的值swap!or 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 hp 并位于 [0 0 0] 位置。如果传递了一个整数或一个向量,它将假定应该是 hp 或 location 的值(分别),否则它会抛出异常。

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

希望其中一些是有帮助的。

于 2012-06-25T19:40:30.223 回答