Использование обобщенных c методов в протоколах Swift - PullRequest
1 голос
/ 05 марта 2020

Я полагаю, у меня есть некоторое недопонимание того, как работают дженерики. У меня есть протокол:

protocol CommandProtocol {
    func execute<T>() -> T
    func unExecute<T>() -> T
}

И класс, который соответствует ему:

class CalculatorCommand: CommandProtocol {
    ...

    func execute<String>() -> String {
        return calculator.performOperation(operator: `operator`, with: operand) as! String
    }

    func unExecute<Double>() -> Double {
        return calculator.performOperation(operator: undo(operator: `operator`), with: operand) as! Double
    }

    ...
}

Метод calculator.performOperation () фактически возвращает Double, но здесь я просто пытаюсь поиграть с дженерики, поэтому я заменяю тип возвращаемого значения с Double на String.

После этого у меня есть класс, который вызывает эти методы:

class Sender {

    ...
    // MARK: - Public methods

    func undo() -> Double {
        if current > 0 {
            current -= 1
            let command = commands[current]
            return command.unExecute()
        }
        return 0
    }

    func redo() -> Double? {
        if current < commands.count {
            let command = commands[current]
            current += 1
            let value: Double = command.execute()
            print(type(of: value))
            return command.execute()
        }
        return nil
    }
    ...
}

В методе undo () все работает как положено (одна вещь, которую я не полностью понял, это то, как на самом деле Swift знает, вернет ли значение unExecute значение Double или нет, или компилятор выведет его на основе возвращаемого типа undo ()?)

Но в методе redo () я вызываю метод execute () который возвращает String, но метод ожидает Double, поэтому я подумал, что моя программа обработает sh, но не работает, он работает совершенно нормально, как если бы метод execute () возвращает Double. Пожалуйста, может кто-нибудь объяснить мне, что именно происходит под прикрытием этого кода? Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 05 марта 2020

Вы думаете, что это возвращает Swift.Double, но нет. Этот код ничем не отличается от использования T вместо Double. Swift не требует, чтобы имена родовых c заполнителей совпадали с тем, что вы указали в протоколе.

func unExecute<Double>() -> Double {
        return calculator.performOperation(operator: undo(operator: `operator`), with: operand) as! Double
    }

Вы на самом деле не ищете обобщенные c методы. Вы хотите этого, вместо этого.

protocol CommandProtocol {
  associatedtype ExecuteValue
  associatedtype UnExecuteValue

  func execute() -> ExecuteValue
  func unExecute() -> UnExecuteValue
}
0 голосов
/ 05 марта 2020

Вы правы в том, что неправильно поняли дженерики. Во-первых, давайте посмотрим на этот протокол:

protocol CommandProtocol {
    func execute<T>() -> T
    func unExecute<T>() -> T
}

Это говорит "независимо от того, какой тип запрашивает вызывающая сторона, эта функция будет возвращать этот тип". Это невозможно успешно реализовать (под «успешно» я имею в виду «правильно возвращает значение во всех случаях без сбоев»). Согласно этому протоколу, мне разрешено писать следующий код:

func run(command: CommandProtocol) -> MyCustomType {
    let result: MyCustomType = command.execute()
    return result
}

Нет способа написать execute, который действительно это сделает, независимо от того, что MyCustomType.

Ваша путаница усугубляется тонкой синтаксической ошибкой:

func execute<String>() -> String {

Это не означает "T = String", что, как я думаю, вы ожидаете, что это будет означать. Он создает переменную типа String (которая не имеет ничего общего с строковым типом Swift) и обещает ее вернуть. когда вы позже напишите as! String, это означает, что «если эти значения не совместимы с запрошенным типом (не« строка », а то, что было запрошено вызывающей стороной), то cra sh.

инструмент, который ведет себя ближе к тому, что вы хотите, это связанный тип. Вы хотели написать это:

protocol CommandProtocol {
    associatedType T
    func execute() -> T
    func unExecute() -> T
}

Но это почти наверняка не будет делать то, что вы хотите. Например, с этим невозможно есть массив команд.

Вместо этого вам, вероятно, нужна структура:

struct Command {
    let execute: () -> Void
    let undo: () -> Void
}

Затем вы создаете команды, передавая замыкания, которые делают то, что вы хотите:

let command = Command(execute: { self.value += 1 }, 
                      undo: { self.value -= 1 })

Альтернативно, поскольку это калькулятор, вы можете сделать это следующим образом:

struct Command {
    let execute: (Double) -> Double
    let undo: (Double) -> Double
}

let command = Command(execute: { $0 + 1 }, undo: { $0 - 1 })

Тогда ваш абонент будет выглядеть так:

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