CSJ*_*CSJ 6 f# functional-programming strategy-pattern
在C#中,我有以下代码:
public class SomeKindaWorker
{
public double Work(Strategy strat)
{
int i = 4;
// some code ...
var s = strat.Step1(i);
// some more code ...
var d = strat.Step2(s);
// yet more code ...
return d;
}
}
Run Code Online (Sandbox Code Playgroud)
这是一段代码,可以通过使用提供的策略对象来填充部分实现来完成某种工作.注意:一般来说,策略对象不包含状态; 它们只是多态地提供各个步骤的实现.
策略类如下所示:
public abstract class Strategy
{
public abstract string Step1(int i);
public abstract double Step2(string s);
}
public class StrategyA : Strategy
{
public override string Step1(int i) { return "whatever"; }
public override double Step2(string s) { return 0.0; }
}
public class StrategyB : Strategy
{
public override string Step1(int i) { return "something else"; }
public override double Step2(string s) { return 4.5; }
}
Run Code Online (Sandbox Code Playgroud)
观察:通过使用lambda(并完全摆脱策略对象)可以在C#中实现相同的效果,但是这个实现的好处是扩展类将它们的Step1和Step2实现在一起.
问题:F#中这个想法的惯用实现是什么?
思考:
我可以将单个步骤函数注入到Work函数中,类似于观察中的想法.
我还可以创建一个收集两个函数的类型,并通过以下方式传递该类型的值:
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 }
Run Code Online (Sandbox Code Playgroud)
这似乎是我想要实现的最接近的匹配:它使实现步骤保持紧密,以便它们可以作为一堆进行检查.但是这个想法(创建一个仅包含函数值的类型)在功能范例中是惯用的吗?还有其他想法吗?
你应该在这里使用F#对象表达式:
type IStrategy =
abstract Step1: int -> string
abstract Step2: string -> double
let strategyA =
{ new IStrategy with
member x.Step1 _ = "whatever"
member x.Step2 _ = 0.0 }
let strategyB =
{ new IStrategy with
member x.Step1 _ = "something else"
member x.Step2 _ = 4.5 }
Run Code Online (Sandbox Code Playgroud)
您可以获得两全其美:继承的灵活性和功能的轻量级语法.
使用函数记录的方法很好,但不是最惯用的方法.以下是F#组件设计指南(第9页)的建议:
在F#中,有许多方法来表示操作字典,例如使用函数元组或函数记录.通常,我们建议您使用接口类型来实现此目的.
编辑:
使用记录更新with很棒但是当记录字段是函数时,intellisense不能很好地工作.使用接口,您可以通过在对象表达式中传递参数来进一步自定义
let createStrategy label f =
{ new IStrategy with
member x.Step1 _ = label
member x.Step2 s = f s }
Run Code Online (Sandbox Code Playgroud)
或者interface IStrategy with在需要更多可扩展性时使用接口实现(它与C#方法相同).
你提到在C#中简单使用lambdas的可能性.对于步骤很少的策略,这通常是惯用的.它真的很方便:
let f step1 step2 =
let i = 4
// ...
let s = step1 i
// ...
let d = step2 s
// ...
d
Run Code Online (Sandbox Code Playgroud)
不需要接口定义或对象表达式; 推断的类型step1和step2就足够了.在没有高阶函数的语言中(我相信这是发明策略模式的设置),你没有这个选项,而是需要,比如接口.
该功能f在这里想必不介意step1和step2有关系.但是如果调用者这样做,没有什么能阻止他将它们捆绑在一个数据结构中.例如,使用@pad的答案,
let x = f strategyA.Step1 strategyA.Step2
// val it = 0.0
Run Code Online (Sandbox Code Playgroud)
一般来说,"惯用方式"取决于您首先考虑战略模式的原因.战略模式是关于将功能拼接在一起; 高阶函数通常也非常有用.
这是解决问题的一种更实用的方法:
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
Run Code Online (Sandbox Code Playgroud)