我正在尝试实现命令模式来控制机器人。我正在使用它来探索如何在F#中实现命令模式。以下是我的实施:
type Walle(position, rotate) =
let (x:float,y:float) = position
let rotation = rotate
member this.Move(distance) =
let x2 = distance * sin (System.Math.PI/180.0 * rotation)
let y2 = distance * cos (System.Math.PI/180.0 * rotation)
let newPosition = (x+x2, y+y2)
Walle(newPosition, rotation)
member this.Rotate(angle) =
let newRotation =
let nr = rotation + angle
match nr with
| n when n < 360.0 -> nr
| _ -> nr - 360.0
Walle(position, newRotation)
let Move distance = fun (w:Walle) -> w.Move(distance)
let Rotate degrees = fun (w:Walle) -> w.Rotate(degrees)
let remoteControl (commands:List<Walle->Walle>) robot =
commands |> List.fold(fun w c -> c w)
let testRobot() =
let commands = [Move(10.0);Rotate(90.0);Move(16.0);Rotate(90.0);Move(5.0)]
let init = Walle((0.0,0.0),0.0)
remoteControl commands init
为了提出一个功能性解决方案,我选择让机器人的动作在每次调用后在其新位置返回一个新的机器人实例(避免变异)。我还使命令函数关闭了执行操作所需的状态。
我很好奇人们在实施模式时是否认为这些是好的设计决策?或者,如果有任何其他建议人们可以给予实施模式吗?
答案 0 :(得分:11)
为了避免采用OO方式将数据与“类型”中的操作相结合并将此组合表示为“对象”,我的POV中更实用的方法是在模块中单独定义数据和操作,如下所示:
module Walle =
type Walle = {Position : float * float; Rotation : float}
let Move distance (w:Walle) =
let x2 = distance * sin (System.Math.PI/180.0 * w.Rotation)
let y2 = distance * cos (System.Math.PI/180.0 * w.Rotation)
{w with Position = (w.Position |> fst) + x2, (w.Position |> snd) + y2 }
let Rotate angle (w:Walle) =
let newRotation =
let nr = w.Rotation + angle
match nr with
| n when n < 360.0 -> nr
| _ -> nr - 360.0
{w with Rotation = newRotation}
现在您可以创建一个新的Walle并使用|&gt;函数将其传递给一系列转换Walle“数据”的函数。这完全是关于数据的数据和转换,没有对象:)。它可能不像命令模式那样更适合OO风格。你真的不需要FP中的模式,还是我们呢?
答案 1 :(得分:1)
对于机器人示例,我宁愿使用命令式样式,即更改机器人对象的状态。因为机器人对象通常具有状态和动作的概念来改变状态。从OO设计的角度来看,某些类型的对象最好是不可变的,例如,.NET中的String,DateTime,但其中很多都不是。
当然,不可变对象具有优势。在问题的持久版本中,您可以保存机器人的所有过去状态,并且可以轻松地在机器人上UnDo
命令。