F# F中的战略模式#
在C#中,我有以下代码:F# F中的战略模式#,f#,functional-programming,strategy-pattern,F#,Functional Programming,Strategy Pattern,在C#中,我有以下代码: 公共类somekindawworker { 公共双重工作(战略战略战略) { int i=4; //一些代码。。。 var s=第1步(i)中的层; //更多的代码。。。 var d=第2步(s)段; //还有更多的代码。。。 返回d; } } 这是一段代码,可以通过使用提供的策略对象填充部分实现来完成某种工作。注:策略对象一般不包含状态;它们只是以多态方式提供各个步骤的实现 strategy类如下所示: 公共抽象类策略 { 公共抽象字符串Step1(inti); 公
公共类somekindawworker
{
公共双重工作(战略战略战略)
{
int i=4;
//一些代码。。。
var s=第1步(i)中的层;
//更多的代码。。。
var d=第2步(s)段;
//还有更多的代码。。。
返回d;
}
}
这是一段代码,可以通过使用提供的策略对象填充部分实现来完成某种工作。注:策略对象一般不包含状态;它们只是以多态方式提供各个步骤的实现
strategy类如下所示:
公共抽象类策略
{
公共抽象字符串Step1(inti);
公共摘要双步骤2(字符串s);
}
公共课策略A:策略
{
公共重写字符串Step1(inti){返回“whatever”;}
公共重写双步骤2(字符串s){返回0.0;}
}
公共课策略B:策略
{
公共重写字符串Step1(inti){返回“其他内容”;}
公共重写双步骤2(字符串s){return 4.5;}
}
观察:在C#中,通过使用lambdas(并完全去掉strategy对象)可以实现相同的效果,但这种实现的好处是扩展类将它们的Step1和Step2实现放在一起
问题:在F#中,这一思想的惯用实现是什么
想法:
我可以在功函数中注入单独的阶跃函数,类似于观察中的想法
我还可以创建一个收集两个函数的类型,并通过以下方式传递该类型的值:
type Strategy = { Step1: int -> string; Step2: string -> double }
let strategyA = { Step1 = (fun i -> "whatever"); Step2 = fun s -> 0.0 }
let strategyB = { Step1 = (fun i -> "something else"); Step2 = fun s -> 4.5 }
这似乎与我试图实现的目标最接近:它将实现步骤紧密地放在一起,以便可以将它们作为一个整体进行检查。但是这个想法(创建一个只包含函数值的类型)在函数范式中是惯用的吗?还有其他想法吗?您应该在这里使用:
您可以充分利用这两个方面:继承的灵活性和类似函数的轻量级语法
let strategyB(setupData) =
let b = 3.0 + setupData
{ new IStrategy with
member x.Step1 _ = "something else"
member x.Step2 _ = 4.5 + b }
您使用函数记录的方法很好,但不是最惯用的方法。以下是(第9页)的建议:
在F#中,有许多方法可以表示操作字典,例如使用函数元组或函数记录。通常,我们建议您为此使用接口类型
目的
编辑:
使用带有的进行记录更新非常好,但如果记录字段是函数,则intellisense无法正常工作。使用接口,您可以通过在对象表达式中传递参数来进一步自定义,例如
let createStrategy label f =
{ new IStrategy with
member x.Step1 _ = label
member x.Step2 s = f s }
或者,当您需要更多的可扩展性时,可以使用接口IStrategy with
(这与C方法相同)。您提到了在C中简单使用lambdas的可能性。对于步骤很少的策略,这通常是惯用的。这真的很方便:
let f step1 step2 =
let i = 4
// ...
let s = step1 i
// ...
let d = step2 s
// ...
d
不需要接口定义或对象表达式;step1
和step2
的推断类型就足够了。在没有高阶函数的语言中(我相信这是发明策略模式的环境),您没有这个选项,而是需要接口
这里的函数f
可能不关心step1
和step2
是否相关。但是,如果调用方这样做了,没有什么可以阻止他将它们捆绑到数据结构中。例如,使用@pad的答案
let x = f strategyA.Step1 strategyA.Step2
// val it = 0.0
通常,“惯用方式”取决于您首先考虑策略模式的原因。战略模式是关于将功能拼接在一起;高阶函数通常也很适合这样做 对象表达式一次只支持一个接口。如果需要两个,请使用类型定义
type IStrategy =
abstract Step1: int -> string
abstract Step2: string -> double
type strategyA() =
let mutable observers = []
interface System.IObservable<string> with
member observable.Subscribe(observer) =
observers <- observer :: observers
{ new System.IDisposable with
member this.Dispose() =
observers <- observers |> List.filter ((<>) observer)}
interface IStrategy with
member x.Step1 _ =
let result = "whatever"
observers |> List.iter (fun observer -> observer.OnNext(result))
result
member x.Step2 _ = 0.0
type SomeKindaWorker() =
member this.Work(strategy : #IStrategy) =
let i = 4
// some code ...
let s = strategy.Step1(i)
// some more code ...
let d = strategy.Step2(s)
// yet more code ...
d
let strat = strategyA()
let subscription = printfn "Observed: %A" |> strat.Subscribe
SomeKindaWorker().Work(strat) |> printfn "Result: %A"
subscription.Dispose()
这允许您初始化策略
SomeKindaWorker().Work(strategyB(2.0)) |> printfn "%A"
这里有一个更实用的方法来解决这个问题:
type Strategy =
| StrategyA
| StrategyB
let step1 i = function
| StrategyA -> "whatever"
| StrategyB -> "something else"
let step2 s = function
| StrategyA -> 0.0
| StrategyB -> 4.5
let work strategy =
let i = 4
let s = step1 i strategy
let d = step2 s strategy
d
这看起来很光滑。尽管有一条评论,strategyA和strategyB值背后的类型现在是匿名的,不能重复使用(即继承);而在记录方法中,您可以通过使用
语法替换其某些步骤,从现有策略中制定新策略。虽然我很喜欢this
/x
值在需要时如何使用。整洁,这就像Java中的匿名类,在我开始使用C#时我真的错过了它(不明白为什么它从未在那里实现;如此方便).我认为在这种情况下,我更愿意从f
的角度保留step1
和step2
函数。我更可能将一对函数捆绑为一个元组,而不是单独提供每一个。反过来,我可能会将它们收集到一个记录类型(我最初的想法)或一个对象表达式(@pad的想法)。我认为这是最佳解决方案的地方,取决于f
实际上在做什么。在许多情况下,尤其是当战略的组成部分是功能性的(没有副作用)时,它们实际上不必相互关联。但是,当然,这远远不是全部。这同时看起来非常有希望,也非常可怕。一方面,语法轻量级且易于理解;另一方面,任何给定策略的“全貌”都分布在不同功能的case语句中。你能对这一点发表评论吗?这是我必须习惯的不同功能分组吗?@CSJ,在我看来,这是FP与OOP的定义特征之一。我在这里更详细地讨论这个问题:这个答案(以及链接的帖子)非常具有描述性和帮助性。链接文章中突出的一点是“OOP鼓励数据和行为的捆绑,而函数式编程鼓励将它们分开”
type Strategy =
| StrategyA
| StrategyB
let step1 i = function
| StrategyA -> "whatever"
| StrategyB -> "something else"
let step2 s = function
| StrategyA -> 0.0
| StrategyB -> 4.5
let work strategy =
let i = 4
let s = step1 i strategy
let d = step2 s strategy
d