Swift 5中如何正确使用子类

How to properly use subclasses in Swift 5

我正在尝试使用通用的 Plugin class 然后是 class 子 PluginOnePluginTwo 来扩展 class 通过添加函数 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)

现在的问题是如何从每个 Plugin 正确创建子classes PluginOnePluginTwo 以便 run() 每个?

我也知道我做错了什么,也许应该立即解码成一个 subclass(如何?)and/or 使用协议而不是 subclasses。

请指教

执行第一个答案的结果:

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

最好的方法当然是协议。但是,如果你想这样做,你可以利用 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!
}

我认为您需要将插件和插件管理分开,因为 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"
}

管理器 class 本身,它有一个从 json 数据添加插件的添加方法,例如 运行All 方法 运行 所有插件

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)
}

在回答“如何正确使用子类”这个问题时,往往是“不要”。 Swift 提供了一种不同的范例:我们采用 WWDC 2016 视频 Protocol and Value Oriented Programming in UIKit Apps 中概述的面向协议编程,而不是面向对象编程。

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”,我们将使用 Encoding and Decoding Custom Types 文档的“手动编码和解码”部分中概述的技术:

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 协议的对象数组。