2015-09-06 12 views
9

A WWDC 2015 session video descrive l'idea di Programmazione orientata al protocollo e voglio adottare questa tecnica nelle mie future app. Ho giocato con Swift 2.0 negli ultimi due giorni per capire questo nuovo approccio e sono bloccato nel tentativo di farlo funzionare con lo Delegate Pattern.Programmazione orientata al protocollo e il pattern delegato

ho due protocolli che definiscono la struttura di base della parte interessante del mio progetto (il codice di esempio è una sciocchezza, ma descrive il problema):

1) Un protocollo delegazione che rende accessibili alcune informazioni, simile a dataSource protocollo di UITableViewController:

protocol ValueProvider { 
    var value: Int { get } 
} 

2) Un protocollo interfaccia del soggetto che fa qualcosa con le informazioni da abo ve (qui è dove l'idea di un approccio "Protocollo-First" entra in gioco):

protocol DataProcessor { 
    var provider: ValueProvider { get } 
    func process() -> Int 
} 

Per quanto riguarda l'effettiva attuazione del Titolare del trattamento, ora posso scegliere tra enumerazioni, le strutture, e le classi. Ci sono diversi livelli di astrazione di come voglio elaborare le informazioni, quindi le classi sembrano adattarsi al meglio (tuttavia non voglio rendere questa decisione definitiva, in quanto potrebbe cambiare in casi d'uso futuri). Posso definire una classe di processore di base, in cima alla quale posso costruire diversi processori casi specifici (non possibile con le strutture e le enumerazioni):

class BaseDataProcessor: DataProcessor { 
    let provider: ValueProvider 

    init(provider: ValueProvider) { 
     self.provider = provider 
    } 

    func process() -> Int { 
     return provider.value + 100 
    } 
} 

class SpecificDataProcessor: BaseDataProcessor { 
    override func process() -> Int { 
     return super.process() + 200 
    } 
} 

Fino a qui tutto funziona come un fascino. Tuttavia, in realtà i processori di dati specifici sono strettamente vincolati ai valori elaborati (al contrario del processore di base, per cui questo è non true), tale che voglio integrare il valore ValueProvider direttamente nella sottoclasse (per confronto: spesso UITableViewControllers è il proprio data source e delegato).

primo momento ho pensato di aggiungere un protocollo di estensione con un'implementazione di default:

extension DataProcessor where Self: ValueProvider { 
    var provider: ValueProvider { return self } 
} 

questo probabilmente funzionerà se non ho avuto la classe BaseDataProcessor che io non voglio fare il valore-bound. Tuttavia, le sottoclassi che ereditano da BaseDataProcessor e adottano ValueProvider sembrano sovrascrivere quella implementazione internamente, quindi questa non è un'opzione.

ho continuato a sperimentare e finito con questo:

class BaseDataProcessor: DataProcessor { 
    // Yes, that's ugly, but I need this 'var' construct so I can override it later 
    private var _provider: ValueProvider! 
    var provider: ValueProvider { return _provider } 

    func process() -> Int { 
     return provider.value + 10 
    } 
} 

class SpecificDataProcessor: BaseDataProcessor, ValueProvider { 
    let value = 1234 

    override var provider: ValueProvider { return self } 

    override func process() -> Int { 
     return super.process() + 100 
    } 
} 

che riunisce e, a prima vista sembra fare quello che voglio. Tuttavia, questa non è una soluzione in quanto produce un ciclo di riferimento, che può essere visto in un parco giochi Swift:

weak var p: SpecificDataProcessor! 
autoreleasepool { 
    p = SpecificDataProcessor() 
    p.process() 
} 
p // <-- not nil, hence reference cycle! 

Un'altra opzione potrebbe essere quella di aggiungere vincoli di classe alle definizioni di protocollo. Tuttavia, questo potrebbe rompere l'approccio POP come lo comprendo.

Concludendo, penso che la mia domanda si riduce a quanto segue: Come si fa a programmare la programmazione orientata ai protocolli e il Delegate Pattern senza limitarsi ai vincoli di classe durante la progettazione del protocollo?

risposta

3

It turns out che usando autoreleasepool in giochi non è adatto per cicli di riferimento prova. Infatti, non vi è alcun ciclo di riferimento nel codice, come si può vedere quando il codice viene eseguito come un'app CommandLine. La domanda rimane ancora se questo è l'approccio migliore. Funziona ma sembra leggermente hacky.

Inoltre, io non sono troppo felice con l'inizializzazione di BaseDataProcessors e SpecificDataProcessors. BaseDataProcessors non dovrebbe conoscere alcun dettaglio di implementazione delle sottoclassi w.r.t. valueProvider e le sottoclassi devono essere discrete rispetto al valore Provider.

Per ora, ho risolto il problema di inizializzazione come segue:

class BaseDataProcessor: DataProcessor { 
    private var provider_: ValueProvider! // Not great but necessary for the 'var' construct 
    var provider: ValueProvider { return provider_ } 

    init(provider: ValueProvider!) { 
     provider_ = provider 
    } 

    func process() -> Int { 
     return provider.value + 10 
    } 
} 

class SpecificDataProcessor: BaseDataProcessor, ValueProvider { 
    override var provider: ValueProvider { return self } // provider_ is not needed any longer 

    // Hide the init method that takes a ValueProvider 
    private init(_: ValueProvider!) { 
     super.init(provider: nil) 
    } 

    // Provide a clean init method 
    init() { 
     super.init(provider: nil) 
     // I cannot set provider_ = self, because provider_ is strong. Can't make it weak either 
     // because in BaseDataProcessor it's not clear whether it is of reference or value type 
    } 

    let value = 1234 
} 

Se hai un'idea migliore, per favore fatemelo sapere :)