定义具有多种消息类型的消息传递域

Defining a Message Passing domain with very many message types

到目前为止,我见过的大多数 F# 消息传递示例都使用 2-4 种消息类型,并且能够利用模式匹配将每条消息定向到其正确的处理程序函数。

对于我的应用程序,由于处理方式和所需参数的不同性质,我需要数百种独特的消息类型。到目前为止,每种消息类型都是它自己的记录类型,并附加了一个标记接口,因为在一个可区分的联合中包含数百种类型不会很漂亮 - 这些类型的模式匹配也不会很漂亮。因此,我目前正在使用反射来查找消息的正确处理函数。

有没有更好、更实用的方法?也许更聪明的方式来定义这样的域?我想在编译时尽可能多地执行正确性,但目前我正在查找基于自定义属性的处理程序函数,并在 运行 时间检查它们的签名。

据我所知,我无法使用 .NET 自定义属性强制执行函数的签名,并且由于有太多类型无法进行实际模式匹配,我不能(据我所知)使用单个通用消息处理函数。我尝试使用通用包装函数作为所有处理程序的 "interface",并且只将自定义属性附加到这个处理程序,但这并没有授予包装函数属性并使它们通过基于该属性的反射可见(我是 .NET 的新手)。

我考虑过将处理函数作为成员附加到它们各自的记录类型的可能性,这将避免反射的需要并在编译时强制执行一些额外的正确性。但是,将所有这些功能都呈现在客户端并没有多大意义。

这个问题有点宽泛,我会试一试。

让我们从类型开始。我们的消息将具有类型和内容。您可以添加更多字段,例如 messageIdsenderreceiver

type MessageType = MessageType of string
type Message<'T> = {
    messageType : MessageType
    message     : 'T
}

同样,我们的处理程序类型会将类型和处理程序函数配对。

type HandlerResult      = Result<string, string>
type MessageHandler<'T> = {
    messageType : MessageType
    handlerF    : Message<'T> -> HandlerResult
}

我们希望有一个地方可以用它们的类型注册所有处理程序。字典是理想的,因为它很快:

let Handlers = System.Collections.Generic.Dictionary<MessageType, MessageHandler<obj>>()

唯一的问题是字典不能有通用类型,所以这里的所有处理程序都将是 MessageHandler<obj> 类型。因此,我们需要能够将 <'T> 消息和处理程序转换为 <obj> 消息和处理程序并返回。 Se这里我们有一个辅助函数:

let ofMessageGen (msg: Message<obj>) : Message<_> = {
    messageType =       msg.messageType
    message     = unbox msg.message
}

和一个将处理程序函数注册为 <obj> 处理程序的函数:

let registerHandler (handlerF:Message<'T> -> HandlerResult) = 
    let handler = {
        messageType = MessageType <| (typeof<'T>).FullName
        handlerF    = ofMessageGen >> handlerF
    }
    Handlers.Add(handler.messageType, handler )

这样我们就可以为任何类型注册处理程序:

registerHandler  (fun msg -> sprintf "String message: %s" msg.message |> Ok )
registerHandler  (fun msg -> sprintf "int    message: %d" msg.message |> Ok )
registerHandler  (fun msg -> sprintf "float  message: %f" msg.message |> Ok )

这是我们的通用消息处理程序:

let genericHandler (msg:Message<obj>) : HandlerResult =
    match Handlers.TryGetValue msg.messageType with
    | false, _       -> Error <| sprintf "No Handler for message: %A" msg
    | true , handler -> handler.handlerF msg

创建消息:

let createMessage (m:'T) = {
    messageType = MessageType <| (typeof<'T>).FullName
    message     = box m
}

然后像这样测试它:

createMessage "Hello" |> genericHandler |> printfn "%A" 
createMessage 123     |> genericHandler |> printfn "%A" 
createMessage 123.4   |> genericHandler |> printfn "%A" 
createMessage true    |> genericHandler |> printfn "%A" 

// Ok "String message: Hello"
// Ok "int    message: 123"
// Ok "float  message: 123.400000"    
// Error
//  "No Handler for message: {messageType = MessageType "System.Boolean";
// message = true;}"