我不会推荐这两种模式中的任何一种。
一个模型应该没有任何视图引用(即它应该没有 UIKit 引用)。您不想将视图(您在屏幕上看到的按钮、图像、镀铬等)和模型(整个棋盘的抽象表示,表示哪些方块有炸弹,哪些没有,哪些方块)混为一谈方块仍然被隐藏并且已经被取消隐藏)。
FWIW,我个人也倾向于将bombCountAround 保留为由Board 为给定行和列计算的属性。它实际上并不是任何给定方格的属性,而是各个方格之间的关系。从理论上讲,您可以拥有一个属性来捕获它,从而不必在运行中重新计算它,但是查看周围八个单元格的计算开销非常小,因此真的没有必要。
举个例子,我可能在棋盘上有一个特定方块的对象:
struct Square {
var isBomb: Bool
var isHidden: Bool
var hasUserFlagged: Bool
}
这个模型代表了我们在开始创建视图时需要了解的有关单元格的所有信息。具体来说,方块是否真的是炸弹,方块是否仍然隐藏(例如用户根本没有点击它),或者用户在那里放置了一个标志来表示他们认为那里是否有炸弹.
顺便说一句,请注意我没有在模型名称中的任何地方使用Cell,因为这通常表示 UIKit 对象,例如集合视图单元格。模型不应该担心视图的实现(即,您是否使用集合视图单元格,或者只是堆栈视图中的按钮,或者只是带有约束的图像视图)。而Cell 暗示了一种视觉元素,而不是板上的抽象方块。
话虽如此,我们可能想要一个结构来代表所有这些方块的整个棋盘,也许是一个代表整个游戏棋盘的Board:
struct Board {
private var squares: [[Square]]
init(width: Int, height: Int, bombCount: Int) {
// build the squares array of array of `Square`, setting a certain number to have bombs
}
}
extension Board {
func isBomb(column: Int, row: Int) -> Bool {
// look up to see if the `Square` at that row and column is a bomb or not
}
func howManyBombsNear(column: Int, row: Int) -> Int {
// calculate how many nearby bombs there are
}
}
现在,这可能不是正确的模型。也许您想将炸弹所在位置的初始设置与游戏时间统计信息(用户未隐藏的内容和带有标记的内容)区分开来。这取决于你。
但要传达的信息是模型应该完全没有任何与视图相关的类型。视图完全从模型中抽象出来,因此您可以根据需要为相关平台实现它(例如,您的非 Catalyst macOS 目标可能使用与 iOS/iPadOS/tvOS 目标完全不同的视图集)。