kraften i delegeret design mønster
29 maj 2019
sidste uge før VM og alle så begejstrede for nye funktioner, som vi vil have lige om et par dage. Lad os dog holde indlæg relateret til DC i næste uge. I denne uge skal vi tale om min favorit design mønster delegeret. Delegeret er det mest enkle og kraftfulde mønster.
i programteknik er delegationsmønsteret et objektorienteret designmønster, der gør det muligt for objektsammensætning at opnå den samme kodegenbrug som en arv. I delegation håndterer et objekt en anmodning ved at delegere til et andet objekt (delegeren). En delegeret er et hjælperobjekt, men med den oprindelige kontekst.
protokoller
vi bruger delegeret mønster hver dag, og iOS SDK bruger det mange steder. For eksempel, Uitablevis delegerede til Uitablevieadatasource befolker tabellen med celler, det delegerer også cellevalg og andre handlinger til Uitablevieadelegate. Et andet glimrende eksempel på delegerede mønstre er Strømcontroller eller koordinatorer. Visningskontrollere delegerer navigationslogik til koordinator. Jeg har adskilt indlæg om at udtrække navigationslogik i Strømcontrollere.
lad os dykke ned i kodeprøver. Antag at du arbejder på et spil. Du udpakkede spillogik i adskilt klassespil, og du vil delegere ændringer i spiltilstand til Uiviecontroller, der gør dette spil.
protocol GameDelegate: AnyObject { func stateChanged(from oldState: Game.State, to newState: Game.State)}class Game { private var state: State = .notStarted { didSet { delegate?.stateChanged(from: oldValue, to: state) } } weak var delegate: GameDelegate? private(set) var value: Int = 0 func start() { state = .started } func generateNextValue() { value = Int.random(in: 0..<1000) state = generateState(using: value) }}extension Game { enum State { case notStarted case started case right case win case lost }}
her er kildekoden til et simpelt spil, der genererer tilfældige værdier. Spilmotoren genererer tilstand baseret på tilfældige værdier. Hver stat ændre opkald delegeret til at passere Gamle og nye stater. Vi definerer vores delegatprotokol udvidet fra AnyObject, det betyder, at den eneste klasseinstans kan acceptere den. Jeg bruger også svagt søgeord til at definere variabel holding delegeret. Det var nødvendigt at bryde fastholdelsescyklussen mellem delegeret og spilklasse. Lad os tage et kig på Gameviecontroller nu.
class GameViewController: UIViewController { private let game: Game init(game: Game) { self.game = game super.init(nibName: nil, bundle: nil) } @IBAction func play() { game.start() } @IBAction func next() { game.generateNextValue() } override func viewDidLoad() { super.viewDidLoad() game.delegate = self }}extension GameViewController: GameDelegate { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } } func stateChanged(from oldState: Game.State, to newState: Game.State) { render(newState) }}
her har vi en Gameviecontroller-klasse, der føder spil med brugerhandlinger og gengiver tilstandsændringer. Gameviecontroller overholder GameDelegate og implementerer alle nødvendige rendering i forlængelse. Som et resultat har vi en komponerbar kodebase ved hjælp af delegeret designmønster.
lukninger
nogle gange, når du kun har en metode i delegaten, kan du erstatte den med lukning. Ideen er den samme, men nu kalder du lukningen og passerer staten i stedet for at kalde metoden efter protokol. Lad os se på eksemplet med lukning.
class Game { typealias StateHandler = (State) -> Void var handler: StateHandler? private var state: State = .notStarted { didSet { handler?(state) } }}class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = { state in self?.render(state) } }}
som du kan se, passerer vi lukningen til spillet klasse instans, der håndterer tilstandsændringer. Vi bruger svag til at bryde fastholdelsescyklussen under lukningens kontekstfangst. En anden mulighed her kan være en brug af det faktum, at enhver Hurtig funktion er en lukning. Så i stedet for at skabe adskilt lukning, kan vi videregive funktionsnavnet. Vær dog forsigtig med, at denne metode skaber behold cirkel. Her er et eksempel på, hvordan vi kan gøre det.
class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = render }}extension GameViewController { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } }}
konklusion
i dag diskuterede vi det mest kraftfulde og ligetil designmønster i iOS-udvikling. Jeg nyder, hvor enkelt det er, og hvor nyttigt det kan være at komponere stykker for at gøre kodebase afkoblet. Du er velkommen til at følge mig på kvidre og stille dine spørgsmål i forbindelse med dette indlæg. Tak for at læse og se dig i næste uge!