Why the Dark Side should use Swift and a SOLID Architecture

39
Why the Dark Side should use Swift and a SOLID Architecture Jorge D. Ortiz-Fuentes @jdortiz #SwiftDarkSide

Transcript of Why the Dark Side should use Swift and a SOLID Architecture

  • Why the Dark Side should use Swift and a SOLID Architecture

    Jorge D. Ortiz-Fuentes @jdortiz

    #SwiftDarkSide

  • A Canonical Examples

    production

    #SwiftDarkSide

  • #SwiftDarkSide

    Agenda

    The Weaknesses of the Dark Side

    SOLID Architecture

    Design patterns

  • Weaknesses of the Dark

    Side

  • Conspiracy Theory

  • Agility and the MVP

  • Moving a Planet

  • SPoF

  • Whose blame is that?

  • Framework dependency

  • Teams can hardly work together

  • Do you see any

    problems?

  • #SwiftDarkSide

    Needs to Address

    Fast growth

    Robustness

    testable

    decoupled

    debuggable (blame)

    Team collaboration

    Reusable

    Defer decisions

    Replaceable frameworks

  • There is a way: SOLID Principles

  • AKA Clean

    Architecture

  • I find your lack of faith disturbing

  • SOLID Architecture

  • Clean ArchitectureAppDelegate

    View (VC) Presenter Interactor Entity Gateway

    Connector

  • #SwiftDarkSide

    Single Responsibility

    MVC is not enough

    More classes, but more cohesive: only one reason to change

  • #SwiftDarkSide

    Business Logicimport Foundation

    class ShowAllSpeakersInteractor {

    // MARK: - Properties let entityGateway: EntityGatewayFetchSpeakersProtocol weak var presenter: SpeakersListPresenterProtocol?

    // MARK: - Initializers init(entityGateway: EntityGatewayFetchSpeakersProtocol) { self.entityGateway = entityGateway } }

    extension ShowAllSpeakersInteractor: InteractorCommandProtocol { func execute() { let entities = entityGateway.fetchAllSpeakers() let displayData = entities.map({entity in return SpeakerDisplayData(speaker: entity)}) presenter?.presentAllSpeakers(displayData) } }

  • #SwiftDarkSide

    Open Close

    Open to Extension, Closed to Modification

  • #SwiftDarkSide

    class SpeakersTableViewController: UITableViewController, SegueHandlerTypeProtocol { static let speakerCellIdentifier = "SpeakerCell" var eventHandler: SpeakersListEventHandlerProtocol? var numberOfRows = 0 override func viewDidLoad() { super.viewDidLoad() eventHandler?.viewDidLoad() }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return numberOfRows }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(SpeakersTableViewController.speakerCellIdentifier, forIndexPath: indexPath) as! SpeakerTableViewCell eventHandler?.presentCell(cell, indexPath: indexPath) return cell } }

    extension SpeakersTableViewController: SpeakersListViewProtocol { func configureListWithNumberOfRows(numberOfRows: Int) { self.numberOfRows = numberOfRows }

    func addRowsAtIndexPaths(indexPaths:[NSIndexPath]) { self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) } }

    Passive View

  • #SwiftDarkSide

    Liskov Substitution

    It should be possible to use derived classes without special care

  • #SwiftDarkSide

    extension SpeakersListsPresenter: SpeakersListEventHandlerProtocol { func viewDidLoad() { interactor.execute() }

    func presentCell(cell: SpeakerCellProtocol, indexPath: NSIndexPath) { let index = indexPath.row guard index < speakers.count else { return } let speaker = speakers[index] cell.displayName(speaker.name) cell.displayTitle(speaker.title) cell.displayDateSubmitted(relativeDateStringFromDate(speaker.dateSubmitted)) } }

    Presentationclass SpeakersListsPresenter { weak var view: SpeakersListViewProtocol? let interactor: ShowAllSpeakersInteractor private var speakers: [SpeakerDisplayData]=[]

    // MARK: - Initializer init(interactor: ShowAllSpeakersInteractor, deleteInteractor: DeleteSpeakerInteractorProtocol) { self.interactor = interactor } }

    extension SpeakersListsPresenter: SpeakersListPresenterProtocol { func presentAllSpeakers(speakers: [SpeakerDisplayData]) { view?.configureListWithNumberOfRows(speakers.count) self.speakers = speakers let addedIndexPaths = speakers.enumerate() .map({(index, _) in return NSIndexPath(forRow: index, inSection: 0)}) view?.addRowsAtIndexPaths(addedIndexPaths) } }

  • #SwiftDarkSide

    Interface Segregation

    Dont force a class to depend on methods that it wont use

    One interactor will not likely use every functionality of the entity gateway

  • #SwiftDarkSide

    class InMemorySpeakersRepo { // MARK: - Properties var speakers: [Speaker] = [] init() { } }

    extension InMemorySpeakersRepo: EntityGatewayFetchSpeakersProtocol { func fetchAllSpeakers() -> [Speaker] { return speakers } }

    extension InMemorySpeakersRepo: EntityGatewayCreateSpeakerProtocol { func createSpeaker(name name: String, title: String, synopsis: String, dateSubmitted: NSDate) { let id = IdentityGenerator.newUUID() speakers.append(Speaker(id: id, name: name, title: title, synopsis: synopsis, dateSubmitted: dateSubmitted)) } }

    extension InMemorySpeakersRepo: EntityGatewayDeleteSpeakerProtocol { func deleteSpeaker(id: String) { speakers = speakers.filter { speaker in speaker.id != id } } }

    Different Functions of the Entity Gateway

  • #SwiftDarkSide

    Dependency Inversion

    The business case SHOULDNT depend on the frameworks

  • #SwiftDarkSide

    Deletion Use Caseclass DeleteSpeakerInteractor { let entityGateway: EntityGatewayDeleteSpeakerProtocol var id: String?

    init(entityGateway: EntityGatewayDeleteSpeakerProtocol) { self.entityGateway = entityGateway } }

    extension DeleteSpeakerInteractor : InteractorCommandProtocol { func execute() { guard let id = self.id else { return } entityGateway.deleteSpeaker(id) } }

  • Design Patterns

  • Take me Outta Herefunc presentCell(cell: SpeakerCellProtocol, indexPath: NSIndexPath) { let index = indexPath.row guard index < speakers.count else { return } let speaker = speakers[index] cell.displayName(speaker.name) cell.displayTitle(speaker.title) cell.displayDateSubmitted(relativeDateStringFromDate(speaker.dateSubmitted)) }

    Know

    That!

  • var control = Houston(fuel: 1.0, astronaut: nil, spaceshipOK: true)

    do { try control.launchSpaceship() } catch Houston.LaunchError.NoFuel { // Add Fuel print("Adding fuel") } catch Houston.LaunchError.NoAstronaut { print("Next in line") } catch Houston.LaunchError.BrokenShip(let problem) { print(problem) } catch let unknowError { // }

    Ready for Lifeclass Houston { let fuel: Double let astronaut: String let spaceshipOK: Bool init (fuel: Double, astronaut: String?, spaceshipOK: Bool) { self.fuel = fuel self.astronaut = astronaut ?? "" self.spaceshipOK = spaceshipOK } enum LaunchError: ErrorType { case NoFuel, NoAstronaut, BrokenShip(String) } func launchSpaceship() throws { guard fuel >= 1.0 else { throw LaunchError.NoFuel } guard astronaut != "" else { throw LaunchError.NoAstronaut } guard spaceshipOK else { throw LaunchError.BrokenShip("Engine") } print("Launching spaceship") } }

    Know

    That!

  • class Text { func displayContents() { print("Hola") } }

    class NullText: Text { override func displayContents() { print(Not much here") } }

    func fetchText() -> Text { return NullText() }

    let text = fetchText() text.displayContents()

    Null Objectclass Text { func displayContents() { print("Hola") } }

    func fetchText() -> Text? { return nil }

    if let text = fetchText() { text.displayContents() }

  • Poor Templateclass PoorTrooper { func getIntoSpaceShip() { print("Commencing countdown engines on") }

    func conquer(target: String) { fatalError() }

    func comeBack() { print("Planet earth is blue") }

    func attack(target: String) { getIntoSpaceShip() conquer(target) comeBack() } }

    let cloneTrooper = PoorTrooper() cloneTrooper.attack("Alderaan")

  • Swifty Templateprotocol Trooper { func getIntoSpaceShip() func conquer(target: String) func comeBack() func attack(target: String) }

    extension Trooper { func getIntoSpaceShip() { print("Commencing countdown engines on") }

    func comeBack() { print("Planet earth is blue") }

    func attack(target: String) { getIntoSpaceShip() conquer(target) comeBack() } }

    class StormTrooper: Trooper { func conquer(target: String) { print("Become part of the Empire, \(target)") } }

    let finn = StormTrooper() finn.attack("Tatooine")

  • Next Steps

  • #SwiftDarkSide

    RecommendationsPay attention to your architecture; It always pays off

    Use Principles to take decisions

    Take advantage of the language and adapt the patterns to it

    In case of doubt, ask for an architecture loan and ship it

  • canonicalexamples.com coupon:

    APPSTERDAMERS16

    http://canonicalexamples.com

  • Thank you!

  • @jdortiz #SwiftDarkSide