【问题标题】:How to refactor this Swift protocol with where clause?如何用 where 子句重构这个 Swift 协议?
【发布时间】:2021-03-11 15:02:02
【问题描述】:

我有一个Savable 协议,它有多个版本的save() 方法,基于C1C2 的一致性。然后还有一个符合Savable协议的Downloadable协议,它根据C1C2的一致性调用Savablesave()方法。

从下面的示例代码可以看出,download() 方法都是一样的。有没有办法通过删除重复的功能来简化这段代码?还是我应该使用另一种完全不同的方法来获得相同的结果?

PS:由于某些未提及的技术原因,SavableDownloadable 都需要成为协议。此外,C1C2 都具有 Self 或关联的类型要求,因此无法执行类型检查。

// Constraint
protocol C1 { }
protocol C2 { }

// Savable
protocol Savable { }
extension Savable {
    func save() {
        print("Perform basic save")
    }
}

extension Savable where Self: C1 {
    func save() {
        print("Perform save C1")
    }
}

extension Savable where Self: C1 & C2 {
    func save() {
        print("Perform save C1 & C2")
    }
}

// Downloadable
protocol Downloadable: Savable { }
extension Downloadable {
    func download() {
        print("Perform download")
        save()
    }
}

extension Downloadable where Self: C1 {
    func download() {
        print("Perform download")
        save()
    }
}

extension Downloadable where Self: C1 & C2 {
    func download() {
        print("Perform download")
        save()
    }
}

// Usage
class MyClassC1: Downloadable, C1 { }
let obj1 = MyClassC1()
obj1.download()
// Output:
// Perform download
// Perform save C1

class MyClassC1C2: Downloadable, C1, C2 { }
let obj2 = MyClassC1C2()
obj2.download()
// Output:
// Perform download
// Perform save C1 & C2

提前致谢!

【问题讨论】:

  • 你应该可以把实现的download()方法放在Donwloadable协议中,任何符合Downloadable协议的类都会有它
  • 我同意,事实上为什么您的任何协议都没有在其中声明函数? protocol Savable { func save() }protocol Downloadable: Savable { func download() }?
  • 我希望所有这些都具有 save()download() 方法所依赖的其他要求,而不是要求提供这些方法的协议。

标签: ios swift protocols


【解决方案1】:

这种方法非常脆弱。您依靠编译时检查来执行重载,并且只有在编译时知道类型时才有效。这种事情只有在save() 的版本之间没有功能差异时才应该做(例如,如果唯一的区别是性能)。协议不是子类。

作为问题的一个例子,考虑:

func getit(what: Downloadable) {
    what.download()
}

getit(what: obj2) // => "Perform basic save", not "perform save C1 & C2"

即使您添加泛型,它也会以完全相同的方式“失败”:

func getit<D: Downloadable>(what: D) { ... }

这并不是真正的失败。所有编译器在编译时知道这是Downloadable类型,所以它会调用Downloadable实现。

如果您想要运行时分派,您需要在运行时使用as? 进行分派。去掉所有的 where 扩展并将 save 替换为:

protocol Savable { }
extension Savable {
    func save() {
        if self is C1 & C2 {
            print("Perform save C1 & C2")
        } else if self is C1 {
            print("Perform save C1")
        } else {
            print("Perform basic save")
        }
    }
}

注意顺序很重要(在测试C1 之前测试C1 &amp; C2)。由您决定如何分派。

不幸的是,这要求您在编译时静态地了解所有类型并将它们放在一个位置。可以解决这个问题并集中注册“保护程序”,但这有点棘手,除非你真的需要它,否则我不特别推荐这个。但是,您可以这样做。

// A Saver object that knows all the "rules". It maps conformances to functions
class Saver {
    static let shared = Saver()

    private var savers: [(predicate: (Any) -> Bool, save: (Any) -> ())] = []

    func addSaver<T>(of: T.Type, save: @escaping (T) -> ()) {
        savers.append((predicate: { $0 is T }, save: { save($0 as! T) }))
    }

    func save(value: Any) {
        for (predicate, save) in savers {
            if predicate(value) {
                save(value)
                return
            }
        }
        fatalError("Couldn't save") // Or some default behavior or whatever
    }
}

// Now you configure the Saver. Order is important!
let saver = Saver.shared
saver.addSaver(of: (C1 & C2).self, save: { _ in print("Perform save C1 & C2") })
saver.addSaver(of: C1.self, save: { _ in print("Perform save C1") })
saver.addSaver(of: Savable.self, save: { _ in print("Perform basic save") })

// Savable
protocol Savable { }
extension Savable {
    func save() {
        // And use it here
        Saver.shared.save(value: self)
    }
}

使用它,您可能会发现许多小问题。就像如果有人想在程序中的某个随机点(可能在另一个模块中?)添加另一个保护程序会发生什么,所以您可能需要重新排序测试,或者如果两个保护程序同样“特定”所以你不知道哪个一个更喜欢。这些正是编译器在处理此类问题时面临的极端情况。

【讨论】:

  • 使用as? 应该可以解决问题,不幸的是,这是不可能的,因为C1C2 在现实生活中都有Self 或关联的类型要求。对不起,我应该在问题中提到它。无论如何,感谢您的全面回复,我会花时间研究您建议的Saver 课程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
相关资源
最近更新 更多