7

对于我的许多家庭项目,我正在逐渐切换到 F#,但对于如何将完整的应用程序连接在一起,尤其是横切关注点,我有点困惑。

在 C# 中,如果我想记录东西,我会使用依赖注入将 ILogger 传递给每个类,然后可以从代码中轻松轻松地调用它。我可以在我的测试中验证是否写入了特定情况下的日志,方法是传入一个模拟并验证它。

public class MyClass
{
    readonly ILogger _logger;
    public MyClass(ILogger logger)
    {
        _logger = logger;
    }

    public int Divide(int x, int y)
    {
        if(y == 0)
        {
            _logger.Warn("y was 0");
            return 0;
        }
        return x / y;
    }
}

在 F# 中我更多地使用模块,所以上面会变成

module Stuff

let divde x y =
    match y with 
    | 0 -> 0
    | _ -> x / y

现在,如果我有一个名为 Logging 的模块,我可以打开它并在 y 为 0 的情况下从那里使用一个 log 函数,但是我如何将它注入到单元测试中呢?

我可以让每个函数获取一个日志函数(字符串 -> 单元),然后使用部分应用程序将它们连接起来,但这似乎是一项非常艰巨的工作,就像创建一个将实际调用包装在日志调用中的新函数一样。是否有我缺少的特定模式或一点 F# 可以做到这一点?(我见过 kprintf 函数,但我仍然不知道如何为各种测试场景指定函数,同时为完整的应用程序使用具体实现)

同样,您将如何存根提取数据的存储库?您是否需要实例化一些类并在其上设置 CRUD 函数,或者是否有办法注入您打开的模块(#define 除外)

4

3 回答 3

3

这是一个基本的答案。首先,您似乎认为类和模块是可互换的。类封装数据,在这个意义上更类似于记录和 DU。另一方面,模块封装了功能(它们被编译为静态类)。所以,我想你已经提到了你的选择:部分函数应用程序,将函数作为数据传递,或者......依赖注入。对于您的特定情况,保留您所拥有的似乎最容易。

另一种方法是使用预处理器指令来包含不同的模块。

#if TESTING 
open LogA
#else
open LogB
#endif

DI 不一定不适合函数式语言。值得指出的是,F# 比 C# 更容易定义和实现接口。

于 2011-09-20T02:05:55.410 回答
3

如果您不需要在运行时更改记录器,那么使用编译器指令或#if在记录器的两个实现之间进行选择(如 Daniel 所建议的)可能是最好和最简单的方法。

从功能的角度来看,依赖注入与通过日志函数参数化所有代码的含义相同。坏事是你需要到处传播这个函数(这让代码有点乱)。您也可以在模块中创建一个全局可变变量并将其设置为某个ILogger接口的实例 - 我认为这实际上是 F# 完全可以接受的解决方案,因为您只需要在几个地方更改此变量。

另一种(更“纯粹”)的替代方法是为日志定义一个工作流(又名 monad)。仅当您使用 F# 编写所有代码时,这才是一个不错的选择。我的书的第 12 章讨论了这个示例,该书可作为免费示例获得。然后你可以写这样的东西:

let write(s) = log {
  do! logMessage("writing: " + s)
  Console.Write(s) }

let read() = log {
  do! logMessage("reading")
  return Console.ReadLine() }

let testIt() = log {
  do! logMessage("starting")
  do! write("Enter name: ")
  let! name = read()
  return "Hello " + name + "!" }

这种方法的好处是它是一种很好的函数式技术。测试代码应该很容易,因为返回的函数Log<'TResult>本质上会给你一个值以及它的副作用的记录,所以你可以比较结果!但是,这可能是一种矫枉过正,因为您必须在log { .. }块中包装使用日志记录的每个计算。

于 2011-09-20T02:37:43.540 回答
0

这是您在 F# 中的 C# 代码的实现,类似于最初的 C#

module stuff

type MyClass(logger:#Ilogger) =
    member x.Divide a b =
        if b=0 then 
            logger.Warn("y was 0")
            0
        else
            x/y

这应该允许您使用从 C# 中知道的相同技术进行日志记录

于 2011-09-20T06:08:09.520 回答