Principio di inversione delle dipendenze (Swift) - Applicabile anche senza polimorfismo? (Astrazione: generici vincolati)

5

Esistono numerosi articoli / blog che spiegano il Principio di inversione delle dipendenze (DIP) usando Swift; per citarne alcuni (i migliori risultati di Google):

Ora, Swift è ampiamente descritto (anche da Apple) come un linguaggio di programmazione orientato al protocollo, piuttosto che un linguaggio OOP; in modo così naturale questi articoli realizzano le astrazioni in DIP utilizzando protocolli anziché ereditarietà, tuttavia protocolli specificamente eterogenei per consentire al livello della politica di utilizzare il polimorfismo per richiamare livelli inferiori senza dover conoscere dettagli di implementazione. Per esempio:.

// Example A
protocol Service {
    func work()
}

final class Policy {
    // Use heterogeneous protocol polymorphically.
    private let service: Service

    init(service: Service) { self.service = service }

    func doWork() { service.work() }
}

final class SpecificService: Service {
    func work() { /* ... */ print("Specific work ...") }
}

let policy = Policy(service: SpecificService())

// Resolves to doWork() of SpecificService at runtime
policy.doWork() // Specific work ...

Mi rendo conto che il polimorfismo è uno dei concetti chiave in DIP (a meno che non mi sbagli), ma da una prospettiva di implementazione di Swift preferirei vedere il DIP applicato usando generici vincolati al protocollo piuttosto che il polimorfismo di runtime. Per esempio:.

// Example B
protocol Service {
    func work()
}

final class Policy<PolicyService: Service> {
    private let service: PolicyService

    init(service: PolicyService) { self.service = service }

    func doWork() { service.work() }
}

final class SpecificService: Service {
    func work() { /* ... */ print("Specific work ...") }
}

let policy = Policy(service: SpecificService())

// policy.service specialized as SpecificService instance at compile time
policy.doWork() // Specific work ...

Non ho visto nessuno usare i generici nel contesto di DIP e Swift, quindi sono probabilmente quello al buio qui, quindi questa domanda.

Come principio design , credo che B sopra abbia lo stesso obiettivo di A , w.r.t. TUFFO; e quando applicato in un linguaggio di programmazione orientato al protocollo tipicamente statico che generalmente preferisce la composizione sull'ereditarietà, in particolare (afaik) protocolli e generici su protocolli e polimorfismo, preferirei usare B . Questo è naturalmente sotto il vincolo che utilizziamo sempre un soloPolicy specializzato alla volta e ci ricolleghiamo al DIP per semplificare le modifiche nei dettagli di basso livello mediante disaccoppiamento / inversione di dipendenza.

Domanda: Sarebbe opportuno che l'Esempio B sia considerato un'applicazione valida di DIP, anche se un% specializzatoPolicy "sa" del concreto Service in fase di compilazione (a causa di generici, ancora disaccoppiati da astrazioni applicate come vincoli al segnaposto generico)?

    
posta dfri 14.01.2018 - 20:52
fonte

1 risposta

4

Disclaimer: non conosco Swift. Con questo fuori posto - se i generici di Swift sono qualcosa come i generici di C #, e sembra che siano un bel po ', non direi che la politica "sa" del servizio concreto, perché in realtà non si può chiamare alcun metodo specifico per il servizio concreto - devi lavorare con ciò che è esposto attraverso il protocollo di servizio. Il fatto che il compilatore sappia come generare una versione in grado di funzionare con il tipo concreto non influisce realmente sull'accoppiamento del codice. È comunque possibile modificare i dettagli di implementazione di qualsiasi servizio concreto senza influire sulla politica, purché aderisca al protocollo del servizio. Quindi questo è conforme al DIP - è solo che i meccanismi coinvolti sono leggermente diversi.

Detto questo, non penso che tu guadagni molto qui (a meno che non ci sia qualcosa di specifico in Swift di cui non sono a conoscenza). Dal punto di vista del codice client, le due varianti sono praticamente le stesse. Dal punto di vista della manutenzione del codice, si sente leggermente confuso (IMO). E non otterrai necessariamente prestazioni migliori, perché, per quanto posso dire, il compilatore genererà probabilmente ancora vtables basati sui vincoli generici e userà dispatch dinamico per chiamare polimorficamente il metodo corretto sul tipo corretto (generici don ' t funzionano allo stesso modo dei template C ++). Certo, il compilatore potrebbe essere in grado di ottimizzarne una parte in determinate situazioni, ma anche così, presumendo che ciò equivalga a un'ottimizzazione prematura da parte tua (e, inoltre, dovresti fare delle misurazioni per determinare se ci sono delle prestazioni effettive guadagna, piuttosto che assumere che ci sarà).

P.S. Il DIP non deve necessariamente coinvolgere i meccanismi linguistici comunemente utilizzati per utilizzarlo, come il polimorfismo basato sull'eredità. Afferma che un componente di alto livello non dovrebbe dipendere direttamente da un componente di basso livello, ma che entrambi dovrebbero dipendere da un'astrazione. Questa astrazione dovrebbe essere "posseduta" (definita da, o prescritta da) il componente di livello superiore. Concettualmente fa parte del componente di livello superiore, se vuoi. Il componente di livello inferiore dovrebbe quindi aderire al "contratto" imposto da questa astrazione, e l'inversione si verifica facendo dipendere il componente di basso livello dagli altri due. Quindi, mentre il ruolo dell'astrazione è spesso realizzato da un protocollo o da una classe base, potresti anche fare qualcosa di abbastanza strano, come avere il componente di alto livello scrivere comandi personalizzati su un file in una posizione nota, che sarebbe quindi letto e interpretato dal componente di basso livello. Qui, l'astrazione è la combinazione del meccanismo di comunicazione, la posizione del file e il set di comandi personalizzati, e ancora, questo è tutto preso per essere definito e posseduto dal componente di alto livello, e il componente di basso livello deve aderire a questi requisiti . È insolito, ma conferma ancora il DIP. Non si tratta dell'implementazione esatta, ma di come i vari elementi interagiscono per controllare l'accoppiamento tra le diverse parti del sistema.

    
risposta data 15.01.2018 - 01:07
fonte

Leggi altre domande sui tag