【问题标题】:How to use protocols for stucts to emulate classes inheritance如何使用结构协议来模拟类继承
【发布时间】:2017-08-24 18:29:05
【问题描述】:

我正在实现一个模型:

  • 它有结构ClientSummaryClientDetails
  • ClientDetails struct 具有 ClientSummary struct 的所有属性 + 一些额外的属性
  • 两个结构都有主初始化器init(jsonDictionary: [String: Any])
  • initsClientSummaryClientDetails 共享大部分代码
  • 有一个扩展可以使用这些结构的共享功能。

我想到的最直接的解决方案就是经典继承,但它不适用于值类型。

我正在尝试使用协议来解决这个问题,但我无法实现那些“共享初始化”。我试图将 init 的共享部分移动到协议扩展,但不能真正做到。有各种错误。

这是测试代码。

protocol Client {
    var name: String { get }
    var age: Int { get }
    var dateOfBirth: Date { get }

    init?(jsonDictionary: [String: Any])
}


struct ClientSummary: Client {
    let name: String
    let age: Int
    let dateOfBirth: Date

    init?(jsonDictionary: [String: Any]) {
        guard let name = jsonDictionary["name"] as? String else {
            return nil
        }
        self.name = name
        age = 1
        dateOfBirth = Date()
    }
}

struct ClientDetails: Client {
    let name: String
    let age: Int
    let dateOfBirth: Date
    let visitHistory: [Date: String]?

    init?(jsonDictionary: [String: Any]) {
        guard let name = jsonDictionary["name"] as? String else {
            return nil
        }
        self.name = name
        age = 1
        dateOfBirth = Date()
        visitHistory = [Date(): "Test"]
    }
}

extension Client {
    // A lot of helper methods here
    var stringDOB: String {
        return formatter.string(from: dateOfBirth)
    }
}

【问题讨论】:

  • 你为什么不直接使用类呢?你需要结构的什么属性来为它牺牲继承?
  • @DávidPásztor 模型的很大一部分确实已经存在于生产环境中。结构体 vs 类是不可行的选择,因为对我们来说使用带有 vs 引用的副本来获得独立状态更有意义。

标签: swift inheritance swift-protocols


【解决方案1】:

继承在这里是错误的工具。说“详细信息IS-A摘要”是没有意义的。细节不是一种种类的总结。抛开它们是否共享很多方法的结构性问题,而专注于一个是否是他者的本质问题。 (有时重命名可以实现这一点,但只要它们是“摘要”和“细节”,继承就没有意义。)

有意义的是说细节HAS-A总结。组合,而不是继承。所以你最终会得到类似的东西:

struct ClientDetails {
    let summary: ClientSummary
    let visitHistory: [Date: String]?

    init?(jsonDictionary: [String: Any]) {
        guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
            return nil
        }
        self.summary = summary
        visitHistory = [Date(): "Test"]
    }

    // You can add these if you need them, or to conform to Client if that's still useful.
    var name: String { return summary.name }
    var age: Int { return summary.age }
    var dateOfBirth: Date { return summary.dateOfBirth }
}

【讨论】:

  • 谢谢罗伯。抱歉,我忘了提 - 我为现有的大型项目重构模型。 ClientDetails 已经实现并在很多地方使用。我认为我没有权限触摸它(至少是它的界面)。我正在实现更小的ClientSummary 结构,并认为我可能可以将通用属性/init 推广到某些协议中。
  • 而不是那样 - 你的观点是 100% 有效的。但不幸的是,我不是从头开始设计这个模型,我必须在我的项目限制范围内采取行动。你能在这里看到除合成之外的任何其他解决方案吗?正如我之前提到的 - 我无法更改 ClientDetails 结构的接口..
  • 我的回答不会改变ClientDetails的界面。它具有与以前相同的界面(如果添加“如果需要,可以添加这些”方法)。如果你不能修改实现,那么继承也不会起作用(事实上,如果你不能触摸ClientDetails.init,我不确定你要解决什么问题)。跨度>
  • 我不知道是我笨还是什么,但我花了一段时间才意识到 ISA 不是某个类名或某种缩写,只是你想让我们读到“是一个“ 响亮地。 HASA 也一样,它是“有一个”。也许在两者之间放置一个空格会消除模糊性。
  • 很抱歉。行话很危险。它们通常写成“is-a”和“has-a”,我应该包括连字符(将修复)。
【解决方案2】:

我经常希望 Swift 有一种内置方法来分离部分 init 方法。然而,它可以用元组来完成,虽然有点尴尬,如下所示:

struct S {
    let foo: String
    let bar: Int
    let baz: Bool

    init() {
        (self.foo, self.bar, self.baz) = S.sharedSetup()
    }

    static func sharedSetup() -> (String, Int, Bool) {
        ...
    }
}

在你的情况下,sharedSetup() 方法可以移动到协议扩展,或者任何方便的地方。

【讨论】:

  • 谢谢。我的结构有超过 3 个属性。我想说ClientSummary 类中有 10 多个属性。我认为这种具有如此大元组的sharedSetup() 方法看起来像是临时破解。
  • 是的,我听说了。在类似的情况下,我实际上所做的是创建一个包含由sharedSetup() 初始化的所有属性的私有结构,并让它返回。
  • 谢谢,Charles Srstka,这个方法帮助我解决了我的一个问题:stackoverflow.com/questions/64942461/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
  • 1970-01-01
  • 2012-10-02
相关资源
最近更新 更多