【问题标题】:Swift way of code organisation: Extensions vs. Classes and Structs快速的代码组织方式:扩展与类和结构
【发布时间】:2017-09-06 00:59:36
【问题描述】:

我目前正在开发一个必须解析 URL 的 iOS 应用程序。必须解析的 URL 始终具有相同的格式。

此类 URL 的示例:

myprotocol://label?firstname=John&lastname=Smith

我的第一个想法是编写一个“解析器”class,它使用 URL 的字符串进行初始化,并具有返回 label、@987654325 的 getter 方法@and lastname 从 URL 中解析出来。

此类的伪代码:

import Foundation

class URLParser {

    var url: URL

    init(url: String) {
        self.url = URL(string: url)
    }

    func label() -> String? {
        // code to determine label
    }

    func firstname() -> String? {
        // code to determine firstname
    }

    func firstname() -> String? {
        // code to determine lastname
    }
}

但后来我阅读了有关 Swift 扩展的更多信息,并看到它们在几个 places 中被使用和推荐。

对于我的用例,我可以为URL 类创建一个扩展,如下所示:

import Foundation

extension URL {

    var label: String {
        // code to determine label
    }

    var firstname: String {
        // code to determine label
    }

    var lastname: String {
        // code to determine label
    }
}

一方面,使用这样的扩展似乎是个好主意,因为它感觉非常轻巧且精确。但另一方面,它也感觉有点不对劲,因为值 labelfirstnamelastname 与 URL 的一般概念没有任何关系。它们特定于我的用例。

我想在我的代码中清楚地传达正在发生的事情,并且使用 MyUseCaseURLParser 之类的类或 URL 的扩展类(继承)称为 MyUseCaseURL 似乎做得更好。

但这一切都是基于过去使用其他语言的编程经验。

Swifty 做这样的事情并围绕它组织代码的方式是什么?除了使用扩展和类之外,还有更好的方法吗?

【问题讨论】:

  • @Honey 这个问题不是关于 where 来实现 protocols 而是更多关于 Swift 代码组织的基础知识,例如我所描述的用例.
  • 听起来你的大部分问题都可以通过 URLComponents 等内置类来处理:developer.apple.com/reference/foundation/urlcomponents
  • 肯定有一些习语被认为是“Swifty”,但在我看来这只是一个设计问题。您应该在惯用的 Swift 中实现您选择的设计,但不要弯曲您的设计以符合该语言用户建议的某些方法。这是一个经典的继承用例;你有一个对象可以做你想做的,但需要一些专业化。通过继承URL,您可以将其传递给任何需要URL 的方法,而无需像使用另一种方法那样不断地包装/解开“内部”URL
  • @Paulw11 URL 是一个struct,但是你不能继承它。

标签: ios swift


【解决方案1】:

有两件事必须表示:

  1. 解析行为
  2. 解析结果

您的第一个解决方案很好,但您不应该将其称为URLParser。解析发生在那里,但类不代表解析器,它代表结果。

我将其简化为:

class MyProtocolData {
    init?(url: URL) {
       // parsing happens here, it's a failable initializer
       // no need to store the URL
    }

    let label: String
    let firstName: String
    let lastName: String
}

您也可以使用struct

第二种解决方案也很好。 Apple 也使用了这样的解决方案(例如在 NSDictionary 上)。但是,这种解决方案的问题是在所有 URL 实例上都具有属性,即使是那些不属于您的方案的实例。此外,每次访问函数(或属性)时,都必须重新解析 URL。

我使用extension 的第一个解决方案是:

extension URL {
   func parseMyProtocol() -> (label: String, firstName: String, lastName: String)? {
   }
}

请注意,我们将解析与结果分开。另请注意,该方法的名称不是通用的,它不会与其他解析方法冲突。

我的第二个解决方案是将结果元组替换为结构:

struct MyProtocolData {
   let label: String
   let firstName: String
   let lastName: String
}

extension URL {
   func parseMyProtocol() -> MyProtocolData? {}
}

您可能会根据 URL 所代表的内容想出更好的名称,例如UserDataparseUserData

如您所见,有多种解决方案,都是惯用的 Swift。使用类没有什么不速之客。

【讨论】:

  • 很好的答案。但是我要问为什么你说这个类不是解析器?将类称为解析器时,您会定义哪些特征? Imo,它是一个 URLParser,它以自定义结构的形式返回结果。
  • 显然,解析器的firstName 是什么?有些人有firstName,但解析器没有名字:) 解析器是某些输入到某些输出的转换器,但您的类实际上代表输出。是的,解析发生在那里,但在里面的某个地方。相反,您可以在解析器上使用 func getResult() 之类的东西,但直接访问结果属性似乎很奇怪。
  • 感谢您的回答。我喜欢结合structextension 的方法以及您对命名的推理。我仍然会等待进一步的答案,但到目前为止,这似乎是最好的答案。
【解决方案2】:

在您的示例中,扩展不适合此。如果您想提供一个函数来检查 URL 是否包含参数,则可以使用扩展。更好的方法是使用带有可失败初始化程序的结构,并使用 let 使其不可变。

struct CustomURL {
    let url: URL
    let label: String
    let firstname: String
    let lastname: String

    init?(url: URL) {
        //Check if url contains all elements you need, if not return nil
    }
}

【讨论】:

  • 感谢您的建议。 structsclasses 的问题在于结构使用值语义,而类使用引用语义。一般来说,我不需要这个特定用例的身份,所以我可以使用struct。但另一方面,除了几个整数和enums 之外,我至少要处理三个Strings。所以我有点担心StackHeap 的分配。一般来说structs 会更好,因为它们只分配在堆栈上。但是,如果struct 有许多必须在Heap 上分配的值,这似乎会改变。
猜你喜欢
  • 2012-04-19
  • 2020-10-11
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多