“ iOS”和“ Swift”宇宙中的“访客”建筑模式

“访问者”是教科书“四人帮”,“ GoF”,“设计模式:可重用的面向对象软件的元素”中描述的行为模式之一。 ”)
简而言之,当需要能够在一组彼此不连接的不同类型的对象上执行相同类型的任何动作时,模板可能会很有用。 或者,换句话说,用同一类型的某个操作或单个来源扩展此系列类型的功能。 同时,可扩展类型的结构和实现不应受到影响。
解释这个想法的最简单方法是举一个例子。

我立即想保留一个例子,它是虚构的,并且是出于学术目的。 本材料旨在介绍OOP的接受,而不是讨论高度专业化的问题。

我还想提请注意,示例中的代码是为了研究设计技术而编写的。 我知道它(代码)的缺点以及改进它以在实际项目中使用的可能性。

例子


假设您有一个UITableViewController的子类型,它使用UITableViewController几个子类型:

 class FirstCell: UITableViewCell { /**/ } class SecondCell: UITableViewCell { /**/ } class ThirdCell: UITableViewCell { /**/ } class TableVC: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(FirstCell.self, forCellReuseIdentifier: "FirstCell") tableView.register(SecondCell.self, forCellReuseIdentifier: "SecondCell") tableView.register(ThirdCell.self, forCellReuseIdentifier: "ThirdCell") } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { /**/ return FirstCell() /**/ return SecondCell() /**/ return ThirdCell() } } 

假设不同亚型的细胞具有不同的高度。

当然,高度计算可以直接放在每种类型的单元的实现中。 但是,如果电池的高度不仅取决于其自身的类型,还取决于任何外部条件,该怎么办? 例如,单元格类型可用于具有不同高度的不同表中。 在这种情况下,我们绝对不希望UITableViewCell子类了解其“超级视图”或“视图控制器”的需求。

然后,可以在UITableViewController方法中执行高度计算:或者使用height值初始化UITableViewCell ,或者UITableViewCell实例转换为特定的子类型,然后在tableView(_:heightForRowAt:)方法中返回不同的值tableView(_:heightForRowAt:) 。 但是,这种方法也可能变得僵化,并变成一长串“ if”运算符或庞大的“ switch”结构。

使用“访问者”模板解决问题


当然,不仅“ Visitor”模板能够解决此问题,而且他也能做到非常优雅。

为此,首先,我们将创建一个类型,该类型实际上将是单元格类型的“访问者”,以及一个仅负责计算表单元格高度的对象:

 struct HeightResultVisitor { func visit(_ ell: FirstCell) -> CGFloat { return 10.0 } func visit(_ ell: SecondCell) -> CGFloat { return 20.0 } func visit(_ ell: ThirdCell) -> CGFloat { return 30.0 } } 

该类型知道所使用的每个子类型,并为每个子类型返回所需的值。

其次, UITableViewCell每个子类型都必须能够“接收”该“访问者”。 为此,我们将使用这种“接收”方法声明一个协议,该协议将由所有使用的单元类型实现:

 protocol HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat } extension FirstCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension SecondCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } extension ThirdCell: HeightResultVisitable { func accept(_ visitor: HeightResultVisitor) -> CGFloat { return visitor.visit(self) } } 

UITableViewController子类内部,可以按如下方式使用该功能:

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! HeightResultVisitable return cell.accept(HeightResultVisitor()) } 

可能会更好!


很可能,我们不想将这样的代码严格地附加到特定功能上。 也许我们希望能够为我们的单元格集添加新功能,但不仅要涉及它们的高度,而且要说例如背景色,单元格内的文本等,而不要与返回值的类型绑定在一起。 具有associatedtype 类型的协议( “具有关联类型的协议”,“ PAT” )将在这里有所帮助:

 protocol CellVisitor { associatedtype T func visit(_ cell: FirstCell) -> T func visit(_ cell: SecondCell) -> T func visit(_ cell: ThirdCell) -> T } 

返回像元高度的实现:

 struct HeightResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> CGFloat { return 10.0 } func visit(_ cell: SecondCell) -> CGFloat { return 20.0 } func visit(_ cell: ThirdCell) -> CGFloat { return 30.0 } } 

在“主机”方面,仅具有一个通用协议及其唯一实现就足够了-对于这种类型的“访问者”。 只有“访问者”各方才知道返回值的不同类型。

该类型的“接收访问者”(在这本书中,“ GoF”这一面称为“元素”)的协议将采用以下形式:

 protocol Visitableell where Self: UITableViewCell { func accept<V: CellVisitor>(_ visitor: V) -> VT } 

(实现类型可能没有任何限制。但是在此示例中,通过UITableViewCell的子类实现此协议没有任何意义。)

及其在UITableViewCell子类型中的实现:

 extension FirstCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension SecondCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } extension ThirdCell: Visitableell { func accept<V: CellVisitor>(_ visitor: V) -> VT { return visitor.visit(self) } } 

最后,使用:

 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! Visitableell return cell.accept(HeightResultCellVisitor()) } 
因此,我们将能够使用“访客”的各种实现创建几乎所有内容,并且从“接收方”不需要任何内容​​来支持新功能。 这个政党甚至都不知道“来宾”到底给予了什么。

另一个例子


让我们尝试使用类似的“访问者”来更改单元格的背景颜色:

 struct ColorResultCellVisitor: CellVisitor { func visit(_ cell: FirstCell) -> UIColor { return .black } func visit(_ cell: SecondCell) -> UIColor { return .white } func visit(_ cell: ThirdCell) -> UIColor { return .red } } 

使用此访问者的示例:

 override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.contentView.backgroundColor = (cell as! Visitableell).accept(ColorResultCellVisitor()) } 

该代码中的某些内容应该令人困惑...一开始,据说“访问者”能够从外部向类添加功能。 那么是否有可能“隐藏”改变单元格背景颜色的所有功能,而不仅仅是从中获得价值? 可以的 然后, associatedtype将采用值Void (aka () -一个空的元组)

 struct BackgroundColorSetter: CellVisitor{ func visit(_ cell: FirstCell) { cell.contentView.backgroundColor = .black } func visit(_ cell: SecondCell) { cell.contentView.backgroundColor = .white } func visit(_ cell: ThirdCell) { cell.contentView.backgroundColor = .red } } 

用法:

 override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { (cell as! Visitableell).accept(BackgroundColorSetter()) } 


而不是结论



乍一看,您可能会喜欢这种模式,但是,必须谨慎使用它。 它在代码中的出现通常可能表明该体系结构中存在更普遍的缺陷。 也许您正在尝试连接不应该连接的东西。 也许增加的功能值得以一种或另一种方式将一个抽象级别提高。

无论哪种方式,几乎任何一种模式都有其优点和缺点,在使用它之前,您应该始终自觉地思考并做出决定。 一方面,模式是一种通用化编程技术的方法,可简化代码的阅读和讨论。 另一方面-解决问题的方法(有时是人为引入的)。 而且,当然,无论如何,不​​要仅仅出于使用它们的事实而狂热地将代码带入所有已知的模式。


我想我完成了! 所有漂亮的代码和更少的“错误”!

我关于设计模式的其他文章:

Source: https://habr.com/ru/post/zh-CN432558/


All Articles