Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Protocol Obsessed Programming (Pragma Conferenc...

Protocol Obsessed Programming (Pragma Conference 2016)

Presented at Pragma Conference 2016.

Protocols are awesome: They allow us to decouple our software better. But do we use them enough? Time for an experiment: Let’s use protocols to excess to find out where they improve, and where they diminish our code.

Allergy warning: This talk may contain traces of Swift.

Avatar for Tammo Freese

Tammo Freese

October 13, 2016
Tweet

More Decks by Tammo Freese

Other Decks in Programming

Transcript

  1. Protocols •In Objective-C: only a few protocols •Foundation (iOS 10):

    356 classes, 26 protocols •In Swift: way more protocols •Int in Foundation (Swift 3): >=13 protocols
  2. Should We Use More Protocols? Without Protocols Protocol Obsessed! Current

    protocol density time “correct” density ? ? ?
  3. Example • Model for collection view content • Adapter to

    the data source • Updates between two models • (Perform updates on collection view) DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  4. CollectionModel public struct CollectionModel { public func numberOfItems(in section: Int)

    -> Int public func cellIdentifier(at indexPath: IndexPath) -> String public func cellModel(at indexPath: IndexPath) -> Any mutating public func addCellWith(identifier: String, model: Any) }
  5. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method CollectionUpdates UICollectionView CollectionModel DataSourceAdapter
  6. DataSourceAdapter open class CollectionDataSourceAdapter { public var collectionModel: CollectionModel public

    let dataSource: UICollectionViewDataSource open func configure(cell: UICollectionViewCell, at indexPath: IndexPath, with model: Any) }
  7. Usage (1) class DataSourceAdapter: CollectionDataSourceAdapter { override func configure(cell: UICollectionViewCell,

    at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  8. Usage (2) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter =

    DataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() var model = CollectionModel() model.addCellWith(identifier: "Cell", model: "A") adapter.collectionModel = model collectionView?.dataSource = adapter.dataSource } … }
  9. CollectionUpdates • Represents the updates between two models • Creation

    via class extension to CollectionModel • Perform updates on UICollectionView via instance method DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  10. CollectionUpdates public struct CollectionUpdates { let indexPathsForInsertedItems: [IndexPath] let indexPathsForDeletedItems:

    [IndexPath] let indexPathsForReloadedItems: [IndexPath] let indexPathsForMovedItems: [IndexPath: IndexPath] func performOn(_ collectionView: UICollectionView) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  11. Usage (3) var toModel = … collectionView.performBatchUpdates({ let fromModel =

    self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  12. CollectionModel • Model for collection view content • Added: builder

    protocol • Added: function for creation DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  13. CollectionModel public protocol CollectionModel { func numberOfItems(in section: Int) ->

    Int func cellIdentifier(at indexPath: IndexPath) -> String func cellModel(at indexPath: IndexPath) -> Any } public protocol CollectionModelBuilder: class { func addCellWith(identifier: String, model: Any) } public func collectionModel(builder: (CollectionModelBuilder) -> Void) -> CollectionModel
  14. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method CollectionUpdates UICollectionView CollectionModel DataSourceAdapter
  15. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method setting a protocol property • Added: function for creation DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  16. DataSourceAdapter public protocol CollectionCellConfigurator { func configure(cell: UICollectionViewCell, at indexPath:

    IndexPath, with model: Any) } public protocol CollectionDataSourceAdapter: class { var collectionModel: CollectionModel { get set } var cellConfigurator: CollectionCellConfigurator? { get set } var dataSource: UICollectionViewDataSource { get } } public func collectionDataSourceAdapter() -> CollectionDataSourceAdapter
  17. Usage (1) (Before) class DataSourceAdapter: CollectionDataSourceAdapter { override func configure(cell:

    UICollectionViewCell, at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  18. Usage (1) (After) struct CellConfigurator: CollectionCellConfigurator { func configure(cell: UICollectionViewCell,

    at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  19. Usage (2) (Before) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter

    = DataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() var model = CollectionModel() model.addCellWith(identifier: "Cell", model: "A") adapter.collectionModel = model collectionView?.dataSource = adapter.dataSource } … }
  20. Usage (2) (After) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter

    = collectionDataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() let model = collectionModel { builder in builder.addCellWith(identifier: "Cell", model: "A") } adapter.collectionModel = model adapter.cellConfigurator = CellConfigurator() collectionView?.dataSource = adapter.dataSource } …
  21. CollectionUpdates • Represents the updates between two models • Creation

    via class extension to CollectionModel • Perform updates on UICollectionView via instance method DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  22. CollectionUpdates • Represents the updates between two models • Creation

    via class protocol extension to CollectionModel • Perform updates on UICollectionView
 target protocol via instance method added through extension DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  23. CollectionUpdates public struct CollectionUpdates { let indexPathsForInsertedItems: [IndexPath] let indexPathsForDeletedItems:

    [IndexPath] let indexPathsForReloadedItems: [IndexPath] let indexPathsForMovedItems: [IndexPath: IndexPath] func performOn(_ collectionView: UICollectionView) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  24. CollectionUpdates public protocol CollectionUpdates { var indexPathsForInsertedItems: [IndexPath] { get

    } var indexPathsForDeletedItems: [IndexPath] { get } var indexPathsForReloadedItems: [IndexPath] { get } var indexPathsForMovedItems: [IndexPath: IndexPath] { get } } public extension CollectionUpdates { func performOn(_ target: CollectionUpdatesTarget) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  25. CollectionUpdatesTarget public protocol CollectionUpdatesTarget { func insertItems(at indexPaths: [IndexPath]) func

    deleteItems(at indexPaths: [IndexPath]) func reloadItems(at indexPaths: [IndexPath]) func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) } extension UICollectionView: CollectionUpdatesTarget { }
  26. Usage (3) (Before) var toModel = … collectionView.performBatchUpdates({ let fromModel

    = self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  27. Usage (3) (After) var toModel = … collectionView.performBatchUpdates({ let fromModel

    = self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  28. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed
  29. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed • Protocols: no generics possible, but associated types
  30. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed • Protocols: no generics possible, but associated types • Challenge: object creation
  31. Information Hiding: One Disadvantage • Strong reference cycle possible •

    Either ignore, or restrict protocol to classes + use weak reference • May require additional property in using class DataSourceAdapter CollectionModel Collection View Controller ✓ ✓ DataSourceAdapter CollectionModel Collection View Controller
  32. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation
  33. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation • you need subclassing (classes)
  34. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation • you need subclassing (classes) • it is your default implementation for a protocol,
 and your protocol obsession only goes so far