【问题标题】:Is using a constant value as a "nil" (absence of value) a good practice in this case? [closed]在这种情况下,将常量值用作“nil”(没有值)是一种好习惯吗? [关闭]
【发布时间】:2016-06-30 18:57:50
【问题描述】:

我正在学习 Swift,但在我的一个模型类中遇到了问题。 我想要做的是拥有一个延迟加载的属性,当它基于的数据发生变化时,它可以“失效”。 (类似这样:https://stackoverflow.com/a/25954243/2382892

我现在拥有的是这样的:

class DataSet {
    var entries: [Entry]

    var average: Double? {
        return self.entries.average
    }
}

Array<Entry>.average 计算 Entry 属性的平均值,但如果数组为空,返回 nil

由于这个平均值的计算成本可能很高,我想懒惰地这样做并存储一个缓存值,仅在必要时重新计算(当DataSet.entries 已修改)。

现在,跟随this answer,我应该这样做:

class DataSet {
    var entries: [Entry]
    var _average: Double?
    var average: Double? {
        if _average == nil {
            _average = self.entries.average
        }
        return _average
    }
}

但是,我在处理 要重新计算平均值 以及 数组为空 并且没有有意义的平均值返回的情况时遇到了问题。

因为我知道平均值总是正数,我可以使用 默认值(例如 -1.0)来表示缓存不再有效,nil 表示有没有条目,反之亦然(nil 表示必须再次计算平均值;-1.0 表示没有条目)。

然而,这似乎并不优雅,或者是实现这种行为的“Swift 方式”(或者确实如此?)。

我该怎么办?

【问题讨论】:

  • 恐怕最佳实践问题不适合 Stack Overflow,因为答案只能基于意见,而不是事实。
  • 你可以使用双选项
  • 顺便说一下,在上面的示例中,您声明了 var average: Double { ... }DataSet 计算属性,但我假设如果没有 Entry 对象,entries.average 将返回 nil。所以我认为声明应该是var average: Double? { ... }
  • 从实践的角度来看,几乎在所有情况下,空数组的平均值 0.0 比 nil 更合适。在过去的 Objective-C 时代,我们习惯于将nil 视为不在乎 ;-)
  • @vadian - 我不同意。 nil 表示没有价值,并且具有永远不会被误认为是真实计算值的优点。您只是在使用另一个魔法值(更糟糕的是,它与潜在的有效值无法区分)。使用0 并不比他最初的-1 方法好。此外,他明确告诉我们“Array<Entry>.average 计算 Entry 属性的平均值,但如果数组为空则返回 nil”(这是正确的方法,恕我直言)。

标签: swift lazy-loading lazy-evaluation


【解决方案1】:

你绝对不应该使用魔法值,比如-1 来表示某种状态。但我同意您不应该使用nil 来表示“缓存的值已失效,必须重新计算”以及“缓存的平均值已计算并且是nil,因为Entry 对象为零” .建议仅将计算值设置为 nil 的其他解决方案的问题在于,它无法区分“无效”和“已计算但 nil”这两种状态,即使您可能会调用 entries.average已经这样做了。诚然,这在计算上可能并不昂贵,但它混淆了两种截然不同的状态。

一个 Swift 解决方案是enum:

class DataSet {
    private enum CachedValue {
        case Calculated(Double?)
        case Invalidated
    }

    var entries = [Entry]() {
        didSet {
            cachedAverage = .Invalidated
        }
    }

    private var cachedAverage: CachedValue = .Invalidated

    var average: Double? {
        switch cachedAverage {
        case .Calculated(let result):
            return result
        case .Invalidated:
            let result = entries.average
            cachedAverage = .Calculated(result)
            return result
        }
    }
}

这会捕获.Invalidated.Calculated 之间的不同状态,并根据需要延迟重新计算。

【讨论】:

【解决方案2】:

您可以使用didSet:

class DataSet {
    var entries: [Entry] {
        didSet {
            /// just mark the average as outdated, it will be
            /// recomputed when someone asks again for it
            averageOutOfDate = true
        }
    }

    /// tells us if we should recompute, by default true
    var averageOutOfDate = true
    /// cached value that avoid the expensive computation
    var cachedAverage: Double? = nil

    var average: Double? {
        if averageOutOfDate {
            cachedAverage = self.entries.average
            averageOutOfDate = false
        }
        return cachedAverage
    }
}

基本上,每当entries 属性值发生变化时,您都会将缓存的值标记为过期,并使用此标志来了解何时重新计算它。

【讨论】:

    【解决方案3】:

    appzYourLife's answer 是一个很好的具体解决方案。但是,我建议采用不同的方法。这就是我要做的:

    我会制定一个协议,定义所有需要从外部访问的重要位。

    然后我会让 2 个结构/类符合这个协议。

    第一个结构/类将是一个缓存层。

    第二个结构/类将是实际的实现,它只会被缓存层访问。

    缓存层将有一个实际实现结构/类的私有实例,并将有一个变量,例如“isCacheValid”,所有使底层数据无效的变异操作(以及通过扩展名,计算值,例如平均值)。

    这种设计使得实际实现的结构/类相当简单,并且完全与缓存无关。

    缓存层完成所有缓存职责,完全不知道如何计算缓存值的细节(因为它们的计算只是委托给实现类。

    【讨论】:

      【解决方案4】:

      使用零

      首先,永远不要使用类型域的特定值来表示没有值。换句话说,不要使用负数来表示没有值。 nil 是这里的答案。

      所以average 应该声明为Double?

      当条目发生变化时清除缓存

      接下来,您需要在每次 entries 发生突变时清除缓存。您可以为此使用didSet

      class DataSet {
          private var entries: [Entry] = [] {
              didSet { cachedAverage = nil }
          }
      
          private var cachedAverage: Double?
          var average: Double? {
              if cachedAverage == nil {
                  cachedAverage = self.entries.average
              }
              return cachedAverage
          }
      }
      

      当条目为空时

      最后,如果您认为空数组的average 应该是nil,那么为什么不相应地更改您添加到SequenceTypeaverage 计算属性?

      【讨论】:

      • 您也可以在数组上使用didSet 来监视突变,而不是将其设为私有。
      • @GriffeyDog:你是对的。我更新了我的答案,谢谢!
      • 这看起来是最简单也可能是最快的解决方案在大多数情况下。但是,正如我所说,SequenceType.average 在序列为空时返回nil,在这种情况下,每次访问DataSet.average 时都会调用SequenceType.average
      猜你喜欢
      • 1970-01-01
      • 2011-08-22
      • 2013-03-30
      • 2020-05-09
      • 2022-10-05
      • 2015-02-18
      • 2016-03-20
      • 1970-01-01
      • 2015-09-02
      相关资源
      最近更新 更多