Swift面向协议的编程 - arturgruchala


2015 年,苹果推出了一个新概念——面向协议的编程。我敢肯定,您正在使用这些技巧中的大部分,甚至全部。
案例:您正在使用UserDefaults. 也许是一个简单的保存用户关于黑暗主题偏好的决定。

class PreferenceSaver {
    var useDarkTheme: Bool {
        get {
            UserDefaults.standard.bool(forKey: "useDarkTheme")
        }
        set {
            UserDefaults.standard.set(newValue, forKey:
"useDarkTheme")
        }
    }
}

到目前为止,这将在生产中完美运行,但是......如果需求发生变化怎么办?也许标准用户默认设置不够好?对于一个属性,更改没有问题,但是如果有 20 个首选项... 我将重构它以使其更有用:)
class PreferenceSaver {
    private let defaults: UserDefaults
    
    init(defaults: UserDefaults = .standard) {
        self.defaults = defaults
    }
    
    var useDarkTheme: Bool {
        get {
            defaults.bool(forKey: "useDarkTheme")
        }
        set {
            defaults.set(newValue, forKey:
"useDarkTheme")
        }
    }
}

没有太大变化,但一切都变了。现在PreferenceSaver有一个新的依赖项,可以在init. 此外,我添加了一个默认值 - 因此代码库中没有任何更改以适应新的初始化。
我们已经完成了一半。现在进行单元测试。要注入UserDefaults模拟,您必须继承并推动整个 Apple 类!这是压倒性的和麻烦的。Defaults 类很简单,那么定位服务、Store Kit 类呢?它们庞大而复杂。
我将再次重构代码。

protocol UserDefaultsProtocol {
    func bool(forKey: String) -> Bool
    func set(_ value: Bool, forKey: String)
}

extension UserDefaults: UserDefaultsProtocol { }

class PreferenceSaver {
    private let defaults: UserDefaultsProtocol
    
    init(defaults: UserDefaultsProtocol = UserDefaults.standard) {
        self.defaults = defaults
    }
    
    var useDarkTheme: Bool {
        get {
            defaults.bool(forKey: "useDarkTheme")
        }
        set {
            defaults.set(newValue, forKey:
"useDarkTheme")
        }
    }
}

再一次,我的更改根本不会影响当前的代码库,因此使用PreferenceSaver. 通过添加协议和扩展UserDefaults,我们拥有原始实现的所有功能,并增加了可测试性!
在测试套件中,将模拟注入PreferenceSaver:

class PreferenceSaverMock: UserDefaultsProtocol {
    var dictionary = [String: Any]()
    func bool(forKey: String) -> Bool {
        return dictionary[forKey] as! Bool
    }
    
    func set(_ value: Bool, forKey: String) {
        dictionary[forKey] = value
    }
}