LSP 和 SwiftUI

LSP and SwiftUI

为了使我的代码可测试,我试图通过让我的 SwiftUI 视图依赖于协议而不是具体类型来遵守 Liskov's substitution principle。这使我可以轻松地交换实现,并允许我轻松地构建用于测试的模拟。这是我正在尝试做的一个例子:

protocol DashboardViewModel: ObservableObject {

  var orders: [Order] { get }
}

我的 DashboardViewModel 需要将更改传达回其依赖项,因此我还附加了 ObservableObject 作为传递要求。

这似乎是个问题。如果您有关联的类型要求,您不能 实现 LSP。这是我从我的 SwiftUI 视图 class 中得到的错误,该错误取决于我的视图模型:

struct DashboardView: View {
  @ObservedObject var viewModel: DashboardViewModel
}

Protocol 'DatastoreProtocol' can only be used as a generic constraint because it has Self or associated type requirements

我最终这样做了:

protocol DashboardViewModel {

  var orders: [Order] { get }
  var objectWillChange: AnyPublisher<Void, Never> { get }
}

这也需要家属做额外的工作来观察状态变化。这剥夺了使用 属性 包装器的便利——主要是依赖者使用 @ObservedObject 观察状态变化的能力。使用这种替代方法会导致我们编写如下代码:

struct DashboardView: View {
  let viewModel: DashboardViewModel

  var viewModelSubscriber: AnyCancellable!

  // MARK: - Used only to force a re-render of this view
  @State private var reload = false

  init(viewModel: DashboardViewModel) {
    self.viewModel = viewModel
    viewModelSubscriber = viewModel.objectWillChange.sink { _ in
      self.reload.toggle()
    } 
  }    
}

这看起来有点令人反感:

  1. 创建了一个仅用于强制更新视图的 @State 变量,因为我们无法利用 SwiftUI 属性 包装器来观察状态变化

  2. 创建了一个 AnyCancellable! 变量来保存视图模型对 objectWillChange 的订阅。这是检测来自 DashboardViewModel

  3. 的状态变化所必需的
  4. 在初始化程序中添加了订阅调用,它只切换 @State 变量以强制从视图模型检索新数据

我觉得应该有更好的方法来处理这个问题。寻求帮助!

解决此问题的一种方法是使您的视图通用:

protocol DashboardViewModel: ObservableObject {
    var orders: [Order] { get }
}

struct DashboardView<Model: DashboardViewModel>: View {
    @ObservedObject var viewModel: Model
}