F#中的策略模式

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)

这似乎是我想要实现的最接近的匹配:它使实现步骤保持紧密,以便它们可以作为一堆进行检查.但是这个想法(创建一个仅包含函数值的类型)在功能范例中是惯用的吗?还有其他想法吗?

pad*_*pad 9

你应该在这里使用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#方法相同).


Sør*_*ois 6

你提到在C#中简单使用lambdas的可能性.对于步骤很少的策略,这通常是惯用的.它真的很方便:

let f step1 step2 = 
    let i = 4
    // ...
    let s = step1 i
    // ...
    let d = step2 s
    //  ...
    d
Run Code Online (Sandbox Code Playgroud)

不需要接口定义或对象表达式; 推断的类型step1step2就足够了.在没有高阶函数的语言中(我相信这是发明策略模式的设置),你没有这个选项,而是需要,比如接口.

该功能f在这里想必不介意step1step2有关系.但是如果调用者这样做,没有什么能阻止他将它们捆绑在一个数据结构中.例如,使用@pad的答案,

let x = f strategyA.Step1 strategyA.Step2
// val it = 0.0 
Run Code Online (Sandbox Code Playgroud)

一般来说,"惯用方式"取决于您首先考虑战略模式的原因.战略模式是关于将功能拼接在一起; 高阶函数通常也非常有用.


Wes*_*ser 5

这是解决问题的一种更实用的方法:

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)