Интерфейсы, возвращающие Any? но бетоны, возвращающие что-то конкретное - PullRequest
0 голосов
/ 28 августа 2018

Мне кажется, я решил эту проблему благодаря текущему ответу 1pointer и обобщению. Я обновлю ответ ниже и добавлю его к этому. Спасибо!

Попытка создать командную шину

Я изо всех сил пытаюсь создать командную шину в 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, поэтому любые скаляры, которые я возвращаю, должны проверять их наличие в контроллере, который создает команду и передает ее на командную шину

Итак, могу ли я использовать дженерики для решения любых моих проблем? Это скорее проблема архитектуры или ОО? Или то, что я ищу, в принципе невозможно, потому что это строго типизированный язык?

Я думаю, что здесь что-то очевидно, что мне не хватает. Как я могу создать то, что мне нужно, при этом сохраняя приличную типизацию, когда мне не нужно рассказывать компилятору все на каждом этапе пути. Это вообще возможно?

Что я пробовал ...

Кто-то предложил мне также использовать протоколы со связанными типами, но я не уверен, куда именно это поставить или как это сделать. Я также подумал о стиле «запрос / ответ», в котором каждая команда возвращает ответ, но это должен быть протокол, который в основном возвращает меня к проблеме Any.

Я также попытался изменить подпись CommandBus на: public func retrieveHandler<T: CommandHandler>(_ command: Command) -> T. Теперь я должен передать команду функции с объявлением типа:

let handler: ConcreteHandlerName = commandBus.retrieveHandler(command)

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Не уверен, что это то, чего вы хотите, потому что я не совсем понимаю, чего вы пытаетесь достичь, но взгляните на этот код 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)

enter image description here

Обновление

Вот CommandBus, который сопоставляется с конкретными экземплярами обработчика, а не только с его типом. Это означает, что метод init из CommandHandlerProtocol можно удалить.

Также я скрыл свойство 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)
0 голосов
/ 29 августа 2018

associatedtype - это ответ на ваш вопрос:

protocol Command { }

protocol CommandHandler {
    associatedtype CommandType: Command // the concrete `CommandType` must conform to `Command`
    associatedtype ReturnType           // each handler will define what the return type is

    func execute(_ command: CommandType) -> ReturnType?
}

struct TestCommand: Command {
    public let value = 1
}

struct TestCommandHandler: CommandHandler {
    typealias CommandType = TestCommand
    typealias ReturnType = Int

    public func execute(_ command: CommandType) -> ReturnType? {
        // now `command` is of type `TestCommand` and the return value is `Int?`
        return 42
    }
}

Я не уверен, что должен делать ваш CommandBus, поэтому я пропускаю эту часть вашего вопроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...