In short, the template can be useful when it is necessary to be able to perform any similar actions on a group of objects of different types that are not related to each other. Or, in other words, to extend the functionality of this series of types by some kind of operation, of the same type or having a single source. In this case, the structure and implementation of extensible types should not be affected.The easiest way to explain an idea is by example.
UITableViewController
that uses several subtypes of UITableViewCell
: 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
subclasses to be aware of the needs of their “superview” or “view controller”.UITableViewController
methods: either initialize the UITableViewCell
together with the height value, or bring the UITableViewCell
instance to a specific subtype and return different values in the tableView(_:heightForRowAt:)
method tableView(_:heightForRowAt:)
. But such an approach can also become inflexible and turn into a long sequence of “if” operators or a cumbersome “switch” structure. 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
should be able to “accept” a given “visitor”. To do this, we will declare a protocol with such a “receiving” method, which will be implemented by all the types of cells used: 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
subclass, the functionality can be used as follows: override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = tableView.cellForRow(at: indexPath) as! HeightResultVisitable return cell.accept(HeightResultVisitor()) }
associatedtype
( “Protocol with Associated Type”, “PAT” ) will help us: 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 } }
protocol Visitableell where Self: UITableViewCell { func accept<V: CellVisitor>(_ visitor: V) -> VT }
UITableViewCell
does not make sense.)UITableViewCell
subtypes: 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()) }
Thus, we will be able to create with the help of different implementations of the “visitor”, in general, almost anything, and the “receiving party” will not need anything to support the new functionality. This party will not even be aware of what specifically for the “guest” has been granted.
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
here will take the value Void
(it is ()
- empty tuple) : 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()) }
Anyway, almost any pattern has its advantages and disadvantages, and before using it you should always think and make a decision consciously. Patterns are, on the one hand, a way to generalize programming techniques for easier reading and discussion of code. On the other hand, there is a way to solve a problem (sometimes, artificially introduced). And, of course, in any case, you should not fanatically bring the code to all known patterns just for the sake of the very fact of their use.
Source: https://habr.com/ru/post/432558/
All Articles