de kracht van Delegate design pattern
29 Mei 2019
vorige week voor WWDC en iedereen die zo enthousiast was over nieuwe functies die we over een paar dagen zullen hebben. Echter, laten we houden berichten met betrekking tot WWDC voor volgende week. Deze week gaan we praten over mijn favoriete design pattern Delegate. Delegate is het meest eenvoudige en krachtige patroon.
in software engineering is het delegatiepatroon een objectgeoriënteerd ontwerppatroon dat objectsamenstelling toestaat om dezelfde code te hergebruiken als een overerving. In delegeren behandelt een object een verzoek door te delegeren aan een tweede object (de gedelegeerde). Een gedelegeerde is een hulpobject, maar met de oorspronkelijke context.
protocollen
we gebruiken Delegate pattern elke dag, en iOS SDK gebruikt het op veel plaatsen. Bijvoorbeeld, UITableView gedelegeerden aan UITableViewDataSource bevolken de tabel met cellen, het delegeert ook cel selectie en andere acties aan UITableViewDelegate. Een ander uitstekend voorbeeld van gedelegeerde patters is FlowController of coördinatoren. ViewControllers delegeert navigatielogica aan coördinator. Ik heb bericht gescheiden over het extraheren van navigatielogica in FlowControllers.
laten we in codevoorbeelden duiken. Stel dat je aan een spel werkt. Je hebt spellogica geëxtraheerd in een gescheiden Klasse spel, en je wilt spelstatuswijzigingen delegeren aan UIViewController die dit spel maakt.
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 }}
hier is de broncode van een eenvoudig spel dat willekeurige waarden genereert. De game engine genereert staat op basis van willekeurige waarden. Elke staat wijzigen oproep delegeren om oude en nieuwe staten passeren. We definiëren ons delegate protocol uitgebreid van AnyObject, dat betekent dat de enige klasse instantie het kan accepteren. Ik gebruik ook zwak trefwoord om variabele holding delegate te definiëren. Het moest de behouden cyclus tussen delegeren en spel klasse te breken. Laten we eens kijken naar GameViewController 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) }}
hier hebben we een GameViewController klasse die het spel voedt met gebruikersacties en statuswijzigingen. GameViewController voldoet aan GameDelegate en implementeert alle benodigde rendering in extensie. Als gevolg daarvan hebben we een composeerbare codebase met behulp van Delegate design pattern.
sluitingen
soms kunt u, als u slechts één methode in de gedelegeerde hebt, deze vervangen door afsluiten. Het idee is hetzelfde, maar nu noem je de sluiting en geef je de staat door in plaats van de methode volgens protocol te noemen. Laten we eens kijken naar het voorbeeld met sluiting.
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) } }}
zoals je kunt zien, geven we de afsluiting door aan de instantie van de spelklasse die statuswijzigingen afhandelt. We gebruiken weak om de retain-cyclus te doorbreken tijdens het vastleggen van de context. Een andere optie hier kan een gebruik van het feit dat elke Swift-functie is een sluiting. Dus in plaats van gescheiden sluiting te maken, kunnen we de functienaam doorgeven. Echter, wees voorzichtig deze methode creëert behouden cirkel. Hier is een voorbeeld van hoe we dat kunnen doen.
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() } }}
conclusie
vandaag bespraken we het krachtigste en meest eenvoudige ontwerppatroon in iOS-ontwikkeling. Ik geniet van hoe eenvoudig het is en hoe nuttig het kan zijn bij het componeren van stukken om codebase ontkoppeld te maken. Voel je vrij om mij te volgen op Twitter en stel uw vragen met betrekking tot dit bericht. Bedankt voor het lezen en tot volgende week!