a power of Delegate design pattern
29 May 2019
a múlt héten, mielőtt WWDC és mindenki annyira izgatott az új funkciók, amelyek Mi lesz csak néhány nap. Tartsuk azonban a WWDC-vel kapcsolatos bejegyzéseket a jövő hétre. Ezen a héten fogunk beszélni a kedvenc tervezési minta küldött. A delegált a legegyszerűbb és legerősebb minta.
a szoftverfejlesztésben a delegálási minta egy objektum-orientált tervezési minta, amely lehetővé teszi az objektumösszetétel számára, hogy ugyanazt a kód újrafelhasználást érje el, mint az öröklés. A delegálás során az objektum úgy kezeli a kérést, hogy delegál egy második objektumra (a delegáltra). A küldött egy segítő objektum, de az eredeti kontextussal.
protokollok
minden nap használjuk a delegált mintát, és az iOS SDK sok helyen használja. Például az UITableView delegálja az uitableviewdatasource-t, amely cellákkal tölti fel a táblát, a cellaválasztást és más műveleteket is delegálja az UITableViewDelegate-be. A delegált minták másik kiváló példája a FlowController vagy a koordinátorok. ViewControllers delegálja navigációs logika koordinátor. Elkülönítettem a navigációs logika Flowcontrollerekbe történő kibontásáról szóló bejegyzést.
merüljünk bele a kódmintákba. Tegyük fel, hogy egy játékon dolgozik. A játék logikáját elválasztott osztályjátékba bontotta, és a játékállapot-változásokat az Uiviewcontrollerre szeretné átruházni, amely ezt a játékot teszi.
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 }}
itt van egy egyszerű játék forráskódja, amely véletlenszerű értékeket generál. A játék motorja véletlenszerű értékek alapján generál állapotot. Minden állam változás hívás küldött át a régi és az új államok. Az AnyObject-ből kiterjesztett delegált protokollt definiáljuk, ami azt jelenti, hogy az egyetlen osztálypéldány képes elfogadni. Gyenge kulcsszót is használok a változó holding delegált meghatározásához. Meg kellett szakítani a megtartási ciklust a delegate és a game class között. Vessünk egy pillantást a Gameviewcontrollerre most.
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) }}
itt van egy GameViewController osztály, amely táplálja a játékot a felhasználói műveletekkel és rendereli az állapotváltozásokat. GameViewController megfelel GameDelegate és végrehajtja az összes szükséges renderelés kiterjesztése. Ennek eredményeként van egy összeállítható kódbázisunk a delegált tervezési minta segítségével.
bezárások
néha, ha csak egy módszer van a küldöttben, akkor azt bezárással helyettesítheti. Az ötlet ugyanaz, de most hívod a lezárást és átadod az állapotot, ahelyett, hogy protokoll szerint hívnád a módszert. Vessünk egy pillantást a bezárással kapcsolatos példára.
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) } }}
mint látható, átadjuk a bezárást a játékosztály példányának, amely kezeli az állapotváltozásokat. A gyengét használjuk a megtartási ciklus megszakítására a Bezárás kontextusának rögzítése során. Egy másik lehetőség itt lehet annak a ténynek a használata, hogy bármely Swift funkció Bezárás. Tehát elválasztott Bezárás létrehozása helyett átadhatjuk a függvény nevét. Legyen azonban óvatos, ez a módszer megteremti megtartja a kört. Íme egy példa arra, hogyan tehetjük ezt meg.
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() } }}
következtetés
ma megvitattuk az iOS fejlesztés legerősebb és legegyszerűbb tervezési mintáját. Élvezem, hogy milyen egyszerű, és mennyire hasznos lehet a darabok komponálásában, hogy a kódbázist leválasztják. Nyugodtan kövess engem a Twitteren, és tedd fel kérdéseidet ezzel a bejegyzéssel kapcsolatban. Köszönöm az olvasást, jövő héten találkozunk!