价值 | 思考 | 共鸣
简评:我们经常需要在编写 方便的代码 和 易于维护的代码 之间取得平衡,当然如果能兼顾两者是最好的。
在平衡便利性和可维护性时,往往会遇到 View 和 Model 需要建立联系的情况,如果 View 和 Model 建立太强的连接会导致代码难以重构和难以重用。
专用 View
先看一个例子,在构建应用时,我们需要创建用于显示特定类型数据的专用视图,假设我们需要在表格中显示用户列表。一个常见的方法是创建一个专门用于渲染用户数据的 UserTableViewCell,如下:
class UserTableViewCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
let imageView = self.imageView!
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = imageView.bounds.height / 2
}
}
我们还需要使用 User model 来填充 Cell 里面的内容,一般会添加一个 configure 方法,用于填充内容,代码如下:
extension UserTableViewCell {
func configure(with user: User) {
textLabel?.text = "\(user.firstName) \(user.lastName)"
imageView?.image = user.profileImage
}
}
上面的代码在功能上没有任何的问题,但是技术上讲,我们实际上已经将 Model 层泄露给我们的 View 层。我们的 UserTableViewCell class 不仅专门用于呈现 User 的信息(不能是其他的数据模型)而且还需要知道 User 对象的具体内容,起初,这可能不是问题,但是如果我们继续沿着这条路走,我们的 View 很容易演变成包含 app 逻辑的代码:
extension UserTableViewCell {
func configure(with user: User) {
textLabel?.text = "\(user.firstName) \(user.lastName)"
imageView?.image = user.profileImage
// Since this is where we do our model->view binding,
// it may seem like the natural place for setting up
// UI events and responding to them.
if !user.isFriend {
let addFriendButton = AddFriendButton()
addFriendButton.closure = {
FriendManager.shared.addUserAsFriend(user)
}
accessoryView = addFriendButton
} else {
accessoryView = nil
}
}
}
编写如上所示的代码可能看起来非常方便,但通常会让程序难以测试和维护。
通用 View
解决上述问题的一个办法是让 View 和 Model 之间进行严格的分离(代码层面和概念层面进行分离)。
我们再回去看看我们 UserTableViewCell 。他不再与 User 耦合,我们可以该名为 RoundedImageTableViewCell 并删除 configure 这个与 User 耦合的方法。
class RoundedImageTableViewCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
let imageView = self.imageView!
imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = imageView.bounds.height / 2
}
}
进行上述更改的好处是,我们现在可以轻松的将 RoundedImageTableViewCell 和 其他 model 配合使用。
但是,在将我们的模型代码与我们的视图代码进行分离时,损失了便利性。之前我们可以使用 configure(with:) 方法来渲染 User,现在我们需要找一种新方法来实现这一点。
我们可以做的是创建一个专用对象来配置 View 的显示。我们来构建一个 UserTableViewCellConfigurator 类,代码如下(不同的架构实现有所不同):
class UserTableViewCellConfigurator {
private let friendManager: FriendManager
init(friendManager: FriendManager) {
self.friendManager = friendManager
}
func configure(_ cell: UITableViewCell, forDisplaying user: User) {
cell.textLabel?.text = "\(user.firstName) \(user.lastName)"
cell.imageView?.image = user.profileImage
if !user.isFriend {
// We create a local reference to the friend manager so that
// the button doesn't have to capture the configurator.
let friendManager = self.friendManager
let addFriendButton = AddFriendButton()
addFriendButton.closure = {
friendManager.addUserAsFriend(user)
}
cell.accessoryView = addFriendButton
} else {
cell.accessoryView = nil
}
}
}
这样我们能够重复使用通用 UI 代码,以及方便的在 View 中呈现 model 数据。并且我们的代码通过依赖注入可以很方便的进行测试,而不是使用单例(这部分内容可以参考这篇文章)。
无论在哪个地方想使用 User 渲染 Cell,我们都可以简单地使用我们的 configurator。
class UserListViewController: UITableViewController {
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let user = users[indexPath.row]
configurator.configure(cell, forDisplaying: user)
return cell
}
}
View 工厂
configurator 非常适合可复用的 View,例如 TableViewCell ,因为他们重用的时候需要不断 re-configurator(重新配置)来显示新的 model,但对于更多的 “static” View,通常只需要配置一次就够了,因为它们渲染的模型在他的生命周期内不会改变。
这种情况下,使用工程模式可能是一个不错的选择。通过这种方式,我们可以将视图创建和配置捆绑在一起,同时仍然保持 UI 代码简单(与任何 model 分离)。
假设我们想要重建一种简单的方式呈现应用消息。我们可能会有一个视图控制器来显示一条消息。以及某种形式的通知视图。当用户收到一条新消息时弹出一个 View,为了避免重复代码我们创建一个 MessageViewFactory 让我们轻松为给定的 message 创建视图。
class MessageViewFactory {
func makeView(for message: Message) -> UIView {
let view = TextView()
view.titleLabel.text = message.title
view.
请发表评论