iOS和Swift Universe中的访客设计模式

(本文最初是用俄语撰写的,并在此处发布。)

访客是由四人帮(GoF)在其经典著作“设计模式:可重用的面向对象软件的元素”中描述的行为设计模式之一。

简而言之,当需要对一组不同类型的对象执行一些类似的操作时,此模式可能很有用。 或根据对象的特定类型执行操作,而不知道该类型。

换句话说,模式可以通过添加相似或具有相同来源的操作来扩展一组类型的功能。 而且,类型结构和实现不会改变。

当然,最简单的解释方法是通过示例说明其用法。

无论如何,我想修正一个事实,即所提供的所有示例和代码段并非取自我的工作经验,而是出于学术目的。 即,本文试图使您熟悉一种 面向对象的技术, 而不是讨论要解决的特殊问题。

我想说的第二件事是,这里的所有代码都是为了更好地理解而结构化的,并且可能存在各种甚至明显的缺陷。

假设我们有一个UITableViewController ,它使用了几个UITableViewCell子类:

  ClassCell类:UITableViewCell {/ ** /} 
SecondCell类:UITableViewCell {/ ** /}
class ThirdCell:UITableViewCell {/ ** /}
  TableVC类:UITableViewController { 
 覆盖func viewDidLoad(){ 
super.viewDidLoad()

tableView.register(FirstCell.self,
forCellReuseIdentifier:“ FirstCell”)
tableView.register(SecondCell.self,
forCellReuseIdentifier:“ SecondCell”)
tableView.register(ThirdCell.self,
forCellReuseIdentifier:“ ThirdCell”)
}
 覆盖func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell { 
/ ** /返回FirstCell()
/ ** /返回SecondCell()
/ ** /返回ThirdCell()
}
  } 

假设不同类型的单元必须具有不同的高度。

当然,可以在特定的像元类型实现中进行高度计算。 但是,如果高价值取决于某些外部条件怎么办? 例如,可以针对不同的表格以不同的方式计算高度。 在这种情况下,我们不希望我们的单元格类型了解其超级视图或视图控制器要求。

如何在UITableViewController计算它呢? 您可以使用高度值初始化UITableViewCell ,或者根据适合传递的IndexPath值的单元格类型从tableView(_:heightForRowAt:) IndexPath值。 但是这种方法并不像我们希望的那样灵活,最终可能会产生一系列长的ifelse条件或笨拙的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子类都必须能够接受这种类型的访问者。 让我们用预期的方法声明一个协议,并通过每个使用的单元格子类实现该协议:

 协议HeightResultVisitable { 
func accept(_访问者:HeightResultVisitor)-> CGFloat
}
 扩展名FirstCell:HeightResultVisitable { 
func accept(_访问者:HeightResultVisitor)-> CGFloat {
返回visitor.visit(个体经营)
}
}
扩展名SecondCell:HeightResultVisitable {
func accept(_访问者:HeightResultVisitor)-> CGFloat {
返回visitor.visit(个体经营)
}
}
扩展ThirdCell:HeightResultVisitable {
func accept(_访问者:HeightResultVisitor)-> CGFloat {
返回visitor.visit(个体经营)
}
}

UITableViewController可以通过以下方式使用它:

 覆写 
func tableView(_ tableView:UITableView,
heightForRowAt indexPath:IndexPath)-> CGFloat {
让单元格= tableView
.cellForRow(at:indexPath)as! 高度结果可见
返回cell.accept(HeightResultVisitor())
}

肯定会更好!

很可能,我们不希望我们的代码严格地专门化。 如果我们需要更改单元格背景颜色并以类似方式进行操作,该怎么办? 关联类型的协议是一个答案。 让我们为访问者定义一个:

 协议CellVisitor { 
相关类型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书中称为Element ):

 协议VisitableСell,其中Self:UITableViewCell { 
func accept (_访问者:V)-> VT
}

(可能没有实现类型的限制,但在此特定示例中,只有UITableViewCell可访问对象才有意义。)

协议特定的实现:

 扩展名FirstCell:VisitableСell{ 
func accept (_访问者:V)-> VT {
返回visitor.visit(个体经营)
}
}
  SecondCell扩展:VisitableСell{ 
func accept (_访问者:V)-> VT {
返回visitor.visit(个体经营)
}
}
 扩展ThirdCell:VisitableСell{ 
func accept (_访问者:V)-> VT {
返回visitor.visit(个体经营)
}
}

并申请:

 覆写 
func tableView(_ tableView:UITableView,
heightForRowAt indexPath:IndexPath)-> CGFloat {
让cell = tableView.cellForRow(at:indexPath)as! 可访问的
返回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}
}

那是可以使用的方式:

 覆盖func tableView(_ tableView:UITableView, 
willDisplay单元格:UITableViewCell,
forRowAt indexPath:IndexPath){
cell.contentView.backgroundColor
=(cell as!VisibleСell).accept(ColorResultCellVisitor())
}

嗯,看起来不像预期的那样好…我们谈论的是访客能够从可扩展类型的外部添加功能。 我们难道不能将功能隐藏在访问者内部,而不是仅仅获取原始值以应用于某个地方,可以吗? 是的,我们可以。 在这种情况下, associatedtype将为Void(也称为空元组—()):

  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
}
  } 

还有:

 覆盖func tableView(_ tableView:UITableView, 
willDisplay单元格:UITableViewCell,
forRowAt indexPath:IndexPath){
(单元格!Visibleable)。accept(BackgroundColorSetter())
}

结论

乍一看,人们可能会开始喜欢这种设计模式,但是必须非常谨慎地使用它。 访客的涌现常常表明一般的建筑缺陷。 可能是有人试图绑定不得绑定的代码部分。 可能必须以某种方式将添加的功能扩展到上层抽象层。 等等。

无论如何,几乎所有的模式都有其优点和缺点,并且应该三思而后行选择的模式是否会改善代码。 自觉下定决心!

一方面,模式是一种通用化编程技术以提高可读性和可理解性的方法。 另一方面,它们是解决问题的一种方法,有时该问题是错误提交的。

当然,仅使用模式就没有意义。

好,今天就这样。 感谢您的阅读,希望您学到了一些新知识! 如果您喜欢这篇文章,可以在这里和Twitter上关注我。

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

– Swift Universe中的迭代器设计模式