草庐IT

swift - 接口(interface)返回任何?但混凝土返回具体的东西

coder 2023-09-15 原文

I believe I have solved this with thanks to the current 1pointer answer and generics. I'll update the answer below and add it to that. Thanks!

正在尝试创建命令总线

我正在努力在 Swift 中创建命令总线。我现在遇到的问题是我试图使这个东西足够通用以处理不同的命令,但结果在很多情况下我必须返回 Any ,这意味着我必须一直对所有内容执行代码内检查,我不确定我能做些什么。

代码

protocol Command { }

struct TestCommand: Command
{ 
    public let value: int = 1
}

protocol CommandHandler
{
    func execute(_ command: Command) -> Any?
}

struct TestCommandHandler: CommandHandler
{
    public func execute(_ command: Command) -> Any?
    {
         // First problem - I have to cast command as! TestCommand
         // due to the interface
    }
}

下面是命令总线,它基本上只是将给它的命令映射到处理程序并返回它。没有什么特别或复杂的。

struct CommandBus
{
    public let container: Container

    /// Added as closures so the commands are only resolved when requested
    private func commandMap() -> Array<[String: () -> (Any)]>
    {
        // TestCommand implements an empty protocol CommandProtocol
        return [
            [String(describing: TestCommand.self): { self.container.resolve(TestCommandHandler.self)! }]
        ]
    }

    /// Dispatch a command to the relevant command handler
    public func dispatch(_ command: Command) -> Any
    {
        let map = self.commandMap()
        let commandName = String(describing: command.self)
        let element = map.enumerated().first(where: { $0.element.keys.first == commandName })
        let elementIndex = map.index(element!.offset, offsetBy: 0)
        let commandHandler: CommandHandler = map[elementIndex].first!.value() as! CommandHandler

        return commandHandler.execute(command)!
    }
}

命令总线的唯一职责实际上是找出要调用的命令处理程序并返回结果。

问题

以下是我现在遇到的问题:

  • 具体的命令处理程序总是必须检查我传入的对象是否属于特定的具体类型,并将其转换为 as。只是为了我可以使用它(在这种情况下,处理程序希望能够从命令中获取 value)
  • 命令总线总是返回 Any所以无论我返回什么标量,都必须检查它们是否存在于创建命令并将其传递到命令总线的 Controller 中

那么 - 我可以使用泛型来解决这里的任何问题吗?这更多是架构问题还是 OO 问题?或者我所追求的基本上是不可能的,因为这是一种严格类型化的语言?

我认为我在这里明显遗漏了一些东西。我怎样才能创建我需要的东西,同时保留体面的类型,而不必在每一步都告诉编译器一切。这可能吗?

我试过的...

有人建议我也可以使用关联类型的协议(protocol),但我不确定将它放在哪里或如何做。我还想到了一个“请求/响应”风格的东西,其中每个命令都返回一个响应,但这必须是一个协议(protocol),它基本上把我带回到 Any问题。

我还尝试更改 CommandBus签名:public func retrieveHandler<T: CommandHandler>(_ command: Command) -> T .我现在必须通过类型声明将命令传递给函数:

let handler: ConcreteHandlerName = commandBus.retrieveHandler(command)

最佳答案

不确定这是否是您想要的,因为我不太了解您要实现的目标,但请看一下我放在一起的这个 Playground 代码:

import Foundation

protocol CommandProtocol {

    associatedtype Handler: CommandHandlerProtocol
    associatedtype Result

}

protocol CommandHandlerProtocol {

    associatedtype Command: CommandProtocol

    func execute(_ command: Command) -> Command.Result
    init()

}

class IntCommand: CommandProtocol {

    typealias Handler = IntCommandHandler
    typealias Result = Int
}

class IntCommandHandler: CommandHandlerProtocol {

    typealias Command = IntCommand

    func execute(_ command: Command) -> Command.Result {
        return 5
    }

    required init() { }
}

class StringCommand: CommandProtocol {

    typealias Handler = StringCommandHandler
    typealias Result = String
}

class StringCommandHandler: CommandHandlerProtocol {

    typealias Command = StringCommand

    func execute(_ command: Command) -> Command.Result {
        return "Hello!"
    }

    required init() { }
}

class CommandBus {

    public var map: [String: Any] = [:]

    func dispatch<T: CommandProtocol>(_ command: T) -> T.Handler.Command.Result {
        let handlerClass = map[String(describing: type(of: command))] as! T.Handler.Type
        let handler = handlerClass.init() as T.Handler
        return handler.execute(command as! T.Handler.Command)
    }

}

let commandBus = CommandBus()
commandBus.map[String(describing: IntCommand.self)] = IntCommandHandler.self
commandBus.map[String(describing: StringCommand.self)] = StringCommandHandler.self

let intResult = commandBus.dispatch(IntCommand())
print(intResult)

let stringResult = commandBus.dispatch(StringCommand())
print(stringResult)

更新

这是一个 CommandBus,它映射到处理程序的特定实例,而不仅仅是它的类型。这意味着可以删除 CommandHandlerProtocol 中的 init 方法。

我还隐藏了 map 属性,而是添加了一个方法来添加映射。这样在创建 map 时您的类型总是正确的:

protocol CommandHandlerProtocol {

    associatedtype Command: CommandProtocol

    func execute(_ command: Command) -> Command.Result

}

class CommandBus {

    private var map: [String: Any] = [:]

    func map<T: CommandProtocol>(_ commandType: T.Type, to handler: T.Handler) {
        map[String(describing: commandType)] = handler
    }

    func dispatch<T: CommandProtocol>(_ command: T) -> T.Handler.Command.Result {
        let handler = map[String(describing: type(of: command))] as! T.Handler
        return handler.execute(command as! T.Handler.Command)
    }

}

let commandBus = CommandBus()
commandBus.map(IntCommand.self, to: IntCommandHandler())
commandBus.map(StringCommand.self, to: StringCommandHandler())

let intResult = commandBus.dispatch(IntCommand())
print(intResult)

let stringResult = commandBus.dispatch(StringCommand())
print(stringResult)

关于swift - 接口(interface)返回任何?但混凝土返回具体的东西,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52065876/

有关swift - 接口(interface)返回任何?但混凝土返回具体的东西的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  3. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  4. ruby-on-rails - link_to 不显示任何 rails - 2

    我试图在索引页中创建一个超链接,但它没有显示,也没有给出任何错误。这是我的index.html.erb代码。ListingarticlesTitleTextssss我检查了我的路线,我认为它们也没有问题。PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articles#createnew_articleGET/article

  5. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  6. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  7. ruby - Ruby 中的隐式返回值是怎么回事? - 2

    所以我开始关注ruby​​,很多东西看起来不错,但我对隐式return语句很反感。我理解默认情况下让所有内容返回self或nil但不是语句的最后一个值。对我来说,它看起来非常脆弱(尤其是)如果你正在使用一个不打算返回某些东西的方法(尤其是一个改变状态/破坏性方法的函数!),其他人可能最终依赖于一个返回对方法的目的并不重要,并且有很大的改变机会。隐式返回有什么意义?有没有办法让事情变得更简单?总是有返回以防止隐含返回被认为是好的做法吗?我是不是太担心这个了?附言当人们想要从方法中返回特定的东西时,他们是否经常使用隐式返回,这不是让你组中的其他人更容易破坏彼此的代码吗?当然,记录一切并给出

  8. ruby-on-rails - ruby 日期方程不返回预期的真值 - 2

    为什么以下不同?Time.now.end_of_day==Time.now.end_of_day-0.days#falseTime.now.end_of_day.to_s==Time.now.end_of_day-0.days.to_s#true 最佳答案 因为纳秒数不同:ruby-1.9.2-p180:014>(Time.now.end_of_day-0.days).nsec=>999999000ruby-1.9.2-p180:015>Time.now.end_of_day.nsec=>999999998

  9. ruby - 从 String#split 返回的零长度字符串 - 2

    在Ruby1.9.3(可能还有更早的版本,不确定)中,我试图弄清楚为什么Ruby的String#split方法会给我某些结果。我得到的结果似乎与我的预期相反。这是一个例子:"abcabc".split("b")#=>["a","ca","c"]"abcabc".split("a")#=>["","bc","bc"]"abcabc".split("c")#=>["ab","ab"]在这里,第一个示例返回的正是我所期望的。但在第二个示例中,我很困惑为什么#split返回零长度字符串作为返回数组的第一个值。这是什么原因呢?这是我所期望的:"abcabc".split("a")#=>["bc"

  10. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

随机推荐