您能否提供一个示例,说明如何表示具有可变属性(例如 HP 和 position(3d 矢量))的“播放器”类,以及诸如 init、setter 和 getter 之类的函数?
3 回答
在真正惯用的 Clojure 中,您的“播放器”将是不可变的,您可能会将其表示为地图,例如:
{:type :player
:team :red
:hit-points 10
:location [17 9 6]}
玩家可能包含在一个更大的“世界”数据结构中,并且会有一个纯函数update-world
可以创建一个带有任何必要修改的新世界(例如将玩家移动到新位置)。
至于 getter / setter - 只需使用法线贴图操作功能。当您只是在操作标准 Clojure 数据时,通常不需要 getter/setter。
您还可以定义记录,它们具有与地图类似的界面和语义,但提供了一些好处。例如,访问记录中的成员比访问地图中的成员要快。此外,您可以在记录上扩展协议并将其用于记录上的快速多态调度。例如,您可以将绘图协议扩展到各种形状对象。根据 Rich Hickey (http://www.infoq.com/interviews/hickey-clojure-reader#) 的说法,使用协议帮助他们使用 Clojurescript 编译器。
例如
(defrecord Action [time key args state]
(comment protocol extension can go here))
我同意 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 的值(分别),否则它会抛出异常。
同样,我认为在大多数情况下,可变数据可能是不必要的,最简单的解决方案可能不是将问题设想为“如何制作这种可变数据结构”,而是“我如何创建新的、更新版本的不可变的数据,然后将这些数据传递回循环的开头,之后我可以更新并再次重复”。
希望其中一些是有帮助的。