Как вызвать перегруженную функцию, когда у вас есть только переменная, соответствующая меньшему типу? - PullRequest
4 голосов
/ 11 июля 2019

Есть ли способ для Swift правильно определить, какую перегруженную функцию вызывать, учитывая переменную, соответствующую общему протоколу каждого из перегруженных параметров?

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

Однако информация о типе «теряется», когда объект понижается до базового протокола. Это мешает мне использовать перегрузку функций в зависимости от типа команды.

Есть ли способ вернуть эту информацию типа "назад", чтобы у Swift было достаточно информации для правильного вызова соответствующей функции?

protocol Command {}
protocol ToDoCommand : Command {}
protocol UserCommand : Command {}

struct AddToDoCommand  : ToDoCommand {}
struct EditToDoCommand : ToDoCommand {}

struct AddUserCommand  : UserCommand {}
struct EditUserCommand : UserCommand {}

class ToDoManager {
  func performCommand(_ command:Command) {
    guard let todoCommand = command as? ToDoCommand else {
      return
    }

    // Perform some tasks that are common to all ToDoCommands...

    // This produces a compiler error because 'todoCommand' is of 
    // type ToDoCommand, which is not specific enough for Swift
    // to deduce which overloaded function to call. Can this
    // be overcome?

    performToDoCommand(todoCommand)
  }

  func performToDoCommand(_ command:AddToDoCommand) {
    print("Add ToDo")
  }

  func performToDoCommand(_ command:EditToDoCommand) {
    print("Edit ToDo")
  }
}

class UserManager {
  func performCommand(_ command:Command) {
    guard let userCommand = command as? UserCommand else {
      return
    }

    // Perform some tasks that are common to all UserCommands...

    // See note above...
    performUserCommand(userCommand)
  }

  func performUserCommand(_ command:AddUserCommand) {
    print("Add User")
  }

  func performUserCommand(_ command:EditUserCommand) {
    print("Edit User")
  }

}

let todoManager = ToDoManager()
let userManager = UserManager()

let command = AddUserCommand()
todoManager.performCommand(command)
userManager.performCommand(command)

Ответы [ 2 ]

2 голосов
/ 12 июля 2019

Я думаю, вы пытаетесь воссоздать наследование классов. Вместо этого вы хотите композицию. Вы создали кучу пустых протоколов, которые действуют как абстрактные классы. Это не правильный способ думать о протоколах. Я думаю, что вы хотите, это просто структуры.

// A command is just something that can be performed
struct Command {
    let perform: () -> Void
}

// And there are lots of ways to make them
extension Command {
    // We can make command types that wrap other command types
    static func makeToDo(additional: @escaping () -> Void) -> Command {
        return Command {
            // common todo behavior
            additional()
        }
    }
}

// And we can just make regular commands
extension Command {
    // Things that include ToDo
    static func makeAddToDo() -> Command { makeToDo { print("Add ToDo") } }
    static func makeEditToDo() -> Command { makeToDo { print("Edit ToDo") }}
    // Things that don't
    static func makeAddUser() -> Command { Command{print("Add User")}}
    static func makeEditUser() -> Command { Command{print("Edit User")}}
}

Теперь нет причины для UserManager, который игнорирует отправленные ему вещи, или ToDoManager, который игнорирует отправленные ему вещи. Это действительно запутанные способы сделать это. «Вот команда. Пожалуйста, выполните ее, если вы знаете, как, но игнорируйте ее, если не знаете». Что вы должны делать, если никто из менеджеров не знает, как выполнить команду? Или несколько менеджеров?

Вместо этого вы просто создаете набор команд и затем вызываете .perform().

2 голосов
/ 12 июля 2019

Есть несколько способов сделать это ...

Используйте switch для восстановления типа

Swift должен знать во время компиляции , какую перегруженную функцию он вызывает. Это не может произойти, если Swift не знает во время компиляции, какой тип переменной он имеет.

Чтобы получить информацию о типе, вы можете использовать switch для восстановления типа:

func performCommand(_ command:Command) {
    guard let todoCommand = command as? ToDoCommand else {
        return
    }

    // Perform some tasks that are common to all ToDoCommands...

    switch todoCommand {
    case let command as AddToDoCommand:
        performCommand(command)
    case let command as EditToDoCommand:
        performCommand(command)
    default: break
    }
}

Использовать полиморфизм

Чтобы позволить Swift решить, какую команду performToDoCommand() запустить во время выполнения, можно использовать полиморфизм .

Добавьте требование для реализации func performToDoCommand() к протоколу ToDoCommand, а затем выполните это для каждого struct, который соответствует ToDoCommand. Позвонить правильно, тогда просто ...

protocol Command {}
protocol ToDoCommand : Command {
    func performToDoCommand()
}
protocol UserCommand : Command {}

struct AddToDoCommand  : ToDoCommand {
    func performToDoCommand() {
        print("Add ToDo")
    }
}

struct EditToDoCommand : ToDoCommand {
    func performToDoCommand() {
        print("Edit ToDo")
    }
}

struct AddUserCommand  : UserCommand {}
struct EditUserCommand : UserCommand {}

class ToDoManager {
    func performCommand(_ command:Command) {
        guard let todoCommand = command as? ToDoCommand else {
            return
        }

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