为什么面向协议编程非常重要

此文系转载,版权归原作者所有。原文从此处获取。

你知道“God Class”(全能类)是什么吗?就是在巨大的文件里,有一个类充满了各种分类、方法和变量。你的App里极有可能就有一些这样的“God Class”(全能类),甚至你自己都不知道。你知道“God Class”(全能类)是什么了吗?你感觉到它会对你的代码库造成毁灭性的影响了吗?往下看,我会告诉你怎么解决这些问题。

“God Class”(全能类)到底是什么?

一般来说,它存在于你的ViewController里,如果你使用的是MVC编程模式的话;如果你使用MVVM,则会在你的ViewModel里。让我来看一个MVVM编程模式下,“God Class”(全能类)的经典例子。

我们假设你正在构建一个约会App,你可能会需要一个UserViewModel来存储相关数据和处理业务逻辑

struct User {
    let id: UUID
    var name: String?
    var phone: String?
    var email: String?
}

class UserViewModel {
    private(set) var user: User?
    init(id: String) {
        self.user = fetchUser(with: id)
    }
    func fetchUser(with id: String) -> User? {
        //fetch user data from the CoreData and optionally return a User object
    }
    func verifyPhone() {
        if let phone = user?.phone {
            // Perform phone number verification
        }
    }
    func verifyEmail() {
        if let email = user?.email {
            // Perform email verification
        }
    }
    func update(name: String) {
        user?.name = name
    }
    func update(email: String) {
        user?.email = email
        verifyEmail()
    }
}
复制代码

内容非常直接了当:

  • 这个类有一个User对象;
  • 有一个初始化方法init(id: String)
  • 有一个func fetchUser(with id: String) -> User?方法,用来从数据库读取用户信息;
  • 有两个验证方法verifyPhoneverifyEmail用来验证用户信息;
  • 有两个更新方法update(name: String)update(email: String)用来更新用户的信息。

如你所见,这个类有三种业务逻辑,混杂在一个类里:

  • 读取信息
  • 验证信息
  • 更新信息

甚至还会有更多复杂的业务逻辑。

这是一个典型的“God Class”(全能类),因为它什么都干。随着工程的不断迭代,“God Class”(全能类)很快就会变得十分臃肿巨大。

接下来,让我们看看面相协议编程能够怎样分解这些逻辑。

面向协议编程到底是什么?

根据Swift documentation的定义:

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.

协议定义了实现一个功能或者特定任务所需的方法、属性以及其它一些必须条件的蓝图。协议能够被类、结构体,以及枚举类型继承,并提供、实现这些必须的条件。任意满足这些协议必须条件的类型就被称之为遵守这个协议。

命名规则

建议使用形容词来命名协议,通常采用后缀ble或者ing

创建以协议为基础的分类和方法

1. UserIdentifiable

// Identify User
protocol UserIdentifiable {
    var user: User?
    init(id: String)
}
复制代码

这个协议包含一个user对象和一个init(id: String)初始化方法。

2. UserFetchable

// Fetch data from the database
protocol UserFetchable { 
    func fetchUser(with id: String) -> User?
}
复制代码

这个协议有一个方法fetchUser(with id: String)用来返回可选User类型对象。

3. UserVerifiable

// Verify user info
protocol UserVerifiable { 
    func verifyPhone() 
    func verifyEmail()
}
复制代码

这个协议包含两个验证方法:verifyPhoneverifyEmail

4. UserUpdatable

// Update user info
protocol UserUpdatable {
    mutating func update(name: String) 
    mutating func update(email: String)
}
复制代码

这个协议包含两个更新方法:update(name: String)update(email: String)。因为我们会更新其它协议里定义的值,所以我们需要mutating关键字来修饰这些方法。

用协议来拆解这个“God Class”(全能类)

注意: 协议可以继承其它的协议以此来获取访问其属性和方法的能力。

实现协议

我们可以简单的在协议下方写一个扩展来实现fetchUser方法

protocol UserFetchable {
    func fetchUser(with id: String) -> User?
}
extension UserFetchable {
    func fetchUser(with id: String) -> User? {
        //fetch user data from the CoreData and optionally return a User object
   }
}
复制代码

对于UserVerifiable协议,因为我们需要使用User对象来取得用户信息,所以我们需要继承UserIdentifiable协议。

protocol UserVerifiable: UserIdentifiable {
    func verifyPhone()
    func verifyEmail()
}
extension UserVerifiable {
    func verifyPhone() {
        if let phone = user?.phone {
            // Perform phone number verification
        }
    }
    func verifyEmail() {
        if let email = user?.email {
            // Perform email verification
        }
    }
}
复制代码

实现UserUpdatable协议时也一样,因为我们需要调用UserVerifiable协议定义的verifyEmail方法,所以我们需要继承UserVerifiable协议。因为UserVerifiable已经继承了UserIdentifiable,所以UserUpdatable可以访问用户信息。

protocol UserUpdatable: UserVerifiable {
    mutating func update(name: String)
    mutating func update(email: String)
}
extension UserUpdatable {
    mutating func update(name: String) {
        user?.name = name
    }
    mutating func update(email: String) {
        user?.email = email
        verifyEmail()
    }
}
复制代码

这样我们基本上就完成了。最后,我们让UserViewModel继承于UserFetchableUserUpdatable
由于我们还没有实现UserIdentifiable协议,而且,把初始化方法放在类定义中也是好的做法。
所以,我们这样实现

struct User {
    let id: UUID
    var name: String?
    var phone: String?
    var email: String?
}
class UserViewModel: UserFetchable, UserUpdatable {
    var user: User?
   
    required init(id: String) {
        self.user = fetchUser(with: id)
    }
}
复制代码

总结

经过以上所有的重构之后,UserViewModel变得更加清晰了。所有的方法和属性都按照他们的功能进行了对应的分组。如果你想要添加,删除或者更新这些方法和属性,你只需要直接去协议中进行修改。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享