Как правильно использовать подклассы в Swift 5 - PullRequest
1 голос
/ 05 октября 2019

Я пытаюсь создать общий класс Plugin, а затем подклассы, такие как PluginOne и PluginTwo, которые расширили бы класс, добавив свойство run() и output, чтобы каждый плагин мог выполнитьПользовательская команда и сохранить вывод.

Примерно так:

class Plugin: Decodable {
  var name: String

  init(name: String) {
    self.name = name
  }
}

class PluginOne: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  func run() {
    // do something
    self.output = "Some output"
  }  
}

class PluginTwo: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  func run() {
    // do something
    self.output = "Some other output"
  }  
}

Теперь я получаю список доступных плагинов из json:

let json = """
[
  { "type": "PluginOne", "name": "First plugin of type one" },
  { "type": "PluginOne", "name": "Second plugin of type one" },
  { "type": "PluginTwo", "name": "abcd" }
]
"""

ИЯ декодирую файл в [Plugin]:

let decoder = JSONDecoder()
let jsonData = Data(json.utf8)
let plugins = try decoder.decode([Plugin].self, from: jsonData)

Теперь вопрос заключается в том, как правильно создать подклассы PluginOne и PluginTwo из каждого Plugin, чтобы run() каждый из них?

Также я понимаю, что я делаю что-то не так и, может быть, должен сразу декодировать в подкласс (как?) И / или использовать протоколы вместо подклассов.

Пожалуйста, сообщите

Результат выполнения первого ответа:

import Foundation

let json = """
[
  { "type": "PluginOne", "name": "First plugin of type one" },
  { "type": "PluginOne", "name": "Second plugin of type one" },
  { "type": "PluginTwo", "name": "abcd" }
]
"""


class Plugin: Decodable {
  var name: String

  init(name: String) {
    self.name = name
  }
}

class PluginOne: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  required init(from decoder: Decoder) throws {
    fatalError("init(from:) has not been implemented")
  }

  func run() {
    // do something
    self.output = "Some output"
  }
}

class PluginTwo: Plugin {
  var output: String?

  init(name: String, output: String) {
    self.output = output

    super.init(name: name)
  }

  required init(from decoder: Decoder) throws {
      fatalError("init(from:) has not been implemented")
  }

  func run() {
    // do something
    self.output = "Some other output"
  }
}

let decoder = JSONDecoder()
let jsonData = Data(json.utf8)
let plugins = try decoder.decode([Plugin].self, from: jsonData)

for plugin in plugins {
    if let pluginOne = plugin as? PluginOne {
        pluginOne.run()
        print(pluginOne.output ?? "empty one")
    }
    else if let pluginTwo = plugin as? PluginTwo {
        pluginTwo.run()
        print(pluginTwo.output ?? "empty two")
    } else {
        print("error")
    }
}
// Result: error error error

Ответы [ 3 ]

1 голос
/ 05 октября 2019

Я думаю, вам нужно разделить плагины и управление плагинами, поскольку json содержит список плагинов для загрузки, создания или запуска, а не настоящие плагины. Поэтому для этого решения я создал отдельный PluginManager для хранения плагинов, а также протокол и перечисление для использования менеджером

protocol Plugin { //Protocol each plugin must conform to
    func run() -> ()
}

enum PluginType: String { // All supported plugins. To simplify the code but not necessary
    case pluginOne = "PluginOne"
    case pluginTwo = "PluginTwo"
}

Сам класс менеджера, он имеет метод add для добавления плагинов из данных json иНапример, метод runAll, который запускает все плагины

struct PluginManager {
    var plugins: [String: Plugin]

    init() {
        plugins = [:]
    }

    mutating func add(_ type: String, name: String) {
        var plugin: Plugin?
        switch PluginType.init(rawValue: type) {
        case .pluginOne:
            plugin = PluginOne()
        case .pluginTwo:
            plugin = PluginTwo()
        default:
            print("warning unknow plugin type: \(type)")
        }
        if let plugin = plugin {
            plugins[name] = plugin
        }
    }

    func runAll() {
        for (name, plugin) in plugins {
            print("Executing \(name)")
            plugin.run()
        }
    }
}

Декодирование JSON упрощено для декодирования в словарь и последующего использования этого словаря для добавления плагинов в менеджер

var pluginManager = PluginManager()
do {
    let plugins = try JSONDecoder().decode([[String: String]].self, from: json)
    for plugin in plugins {
        if let type = plugin["type"], let name = plugin["name"] {
            pluginManager.add(type, name: name)
        }
    }
    pluginManager.runAll()

} catch {
    print(error)
}
1 голос
/ 05 октября 2019

В ответ на вопрос «как правильно использовать подклассы» часто стоит «не надо». Swift предлагает другую парадигму: вместо объектно-ориентированного программирования мы используем протоколно-ориентированное программирование, как описано в видео WWDC 2016 Протоколно-ориентированное программирование в приложениях UIKit .

protocol Plugin: Decodable {
    var name: String { get }

    func run()
}

struct PluginOne: Plugin {
    let name: String

    func run() { ... }
}

struct PluginTwo: Plugin {
    let name: String

    func run() { ... }
}

Вопрос в том,затем «как анализировать JSON», и мы будем использовать методы, описанные в разделе «Кодирование и декодирование вручную» пользовательских типов кодирования и декодирования документ:

struct Plugins: Decodable {
    let plugins: [Plugin]

    init(from decoder: Decoder) throws {
        enum AdditionalInfoKeys: String, CodingKey {
            case type
            case name
        }

        var plugins: [Plugin] = []

        var array = try decoder.unkeyedContainer()

        while !array.isAtEnd {
            let container = try array.nestedContainer(keyedBy: AdditionalInfoKeys.self)

            let type = try container.decode(PluginType.self, forKey: .type)
            let name = try container.decode(String.self, forKey: .name)

            switch type {
            case .pluginOne: plugins.append(PluginOne(name: name))
            case .pluginTwo: plugins.append(PluginTwo(name: name))
            }
        }

        self.plugins = plugins
    }
}

С помощью

enum PluginType: String, Decodable {
    case pluginOne = "PluginOne"
    case pluginTwo = "PluginTwo"
}

Вы можете делать такие вещи, как:

do {
    let plugins = try JSONDecoder().decode(Plugins.self, from: data)
    print(plugins.plugins)
} catch {
    print(error)
}

Это даст вам массив объектов, соответствующих протоколу Plugin.

1 голос
/ 05 октября 2019

Лучший метод - это протоколы. Однако, если вы хотите сделать это, вы можете воспользоваться приятной опциональной функцией приведения Swift:

for plugin in plugins {
    if let pluginOne = plugin as? PluginOne {
        pluginOne.foo = 0// If you need to set some subclass-specific variable
        pluginOne.run()
    }
    else if let pluginTwo = plugin as? PluginTwo {
        pluginTwo.bar = 0
        pluginTwo.run()
    }
}

Если вы хотите использовать протоколы вместо:

protocol Runnable {//Our new protocol, only containing one method
    func run()
}

class Plugin: Decodable {
    name: String

    init(name: String) {
        self.name = name
    }
}

class PluginOne: Plugin, Runnable { //implements Runnable protocol
    output: String?

    init(name: String) {
        self.output = output

        super.init(name: name)
    }

    func run() {
        // do something
        self.output = "Some output"
    }  
}

class PluginTwo: Plugin, Runnable { //Also implements runnable protocol
    output: String?

    init(name: String) {
        self.output = output

        super.init(name: name)
    }

    func run() {
        // do something
        self.output = "Some other output"
    }  
}

//.....

let plugins: [Runnable] = load("plugins.json")//Now we have an array of Runnables!
for plugin in plugins {
    plugin.run()//We know that plugin will implement the Runnable protocol,
                //so we know it contains the run() method!
}
...