Ma il metodo draw () non dipende molto dall'interfaccia utente?
Da un tipo di vista pragmatico, alcuni codici nel tuo sistema devono sapere come disegnare qualcosa come un Rectangle
se questo è un requisito di fine utente. E questo a un certo punto si ridurrà a fare cose di basso livello come rasterizzare i pixel o visualizzare qualcosa in una console.
La domanda per me dal punto di vista dell'accoppiamento è chi / cosa dovrebbe dipendere da questo tipo di informazioni, e da quale grado di dettaglio (quanto astratto, ad esempio)?
Capacità di disegno / rendering astratte
Perché se il codice di disegno di livello superiore dipende solo da qualcosa di molto astratto, quell'astrazione potrebbe essere in grado di funzionare (attraverso la sostituzione di implementazioni concrete) su tutte le piattaforme che si intende targetizzare. Ad esempio, alcune IDrawer
interfacce molto astratte potrebbero essere implementate in entrambe le API della console e della GUI per fare cose come le forme della trama (l'implementazione della console potrebbe trattare la console come qualche "immagine" 80xN con l'arte ASCII). Naturalmente questo è un esempio inventato dal momento che di solito non è ciò che si desidera fare è trattare un output della console come un buffer di immagine / frame; in genere, la maggior parte delle esigenze dell'utente finale richiede più interazioni testuali nelle console.
Un'altra considerazione è quanto è facile progettare un'astrazione stabile? Perché potrebbe essere facile se tutto ciò che stai prendendo di mira sono le moderne API della GUI per sottrarre le funzionalità di base del disegno di forma come tracciare linee, rettangoli, percorsi, testo, cose di questo tipo (solo una semplice rasterizzazione 2D di un insieme limitato di primitive) , con un'unica interfaccia astratta che può essere facilmente implementata per tutti loro attraverso vari sottotipi con costi ridotti. Se riesci a progettare un'astrazione del genere in modo efficace e implementarla su tutte le piattaforme di destinazione, direi che è un male molto minore, se non addirittura un male, per una forma o un controllo della GUI o qualsiasi altra cosa sapere come disegnarsi usando un tale astrazione.
Supponiamo che tu stia cercando di sottrarre i dettagli cruenti che variano tra una Playstation portatile, un iPhone, un XBox One e un potente PC da gioco, mentre le tue esigenze sono di utilizzare le più avanzate tecniche di rendering / ombreggiatura 3D in tempo reale su ognuna . In tal caso, provare a trovare un'interfaccia astratta per sottrarre i dettagli di rendering quando le funzionalità hardware e le API sottostanti variano così selvaggiamente è quasi certo che si tradurrà in tempi enormi di progettazione e riprogettazione, un'alta probabilità di cambiamenti di progettazione ricorrenti con imprevisti scoperte, e allo stesso modo una soluzione con il minimo comune denominatore che non riesce a sfruttare la piena unicità e la potenza dell'hardware sottostante.
Far scorrere le dipendenze verso progetti stabili e "facili"
Nel mio campo sono in quest'ultimo scenario. Puntiamo a un sacco di hardware diverso con capacità e API sottostanti radicalmente diverse e provare a trovare un'astrazione di rendering / disegno per dominarli è del tutto senza speranza (potremmo diventare famosi in tutto il mondo semplicemente facendo ciò efficacemente come sarebbe un gioco cambia nel settore). Quindi l'ultima cosa che voglio nel mio caso è come il analogica Shape
o Model
o Particle Emitter
che sa come disegnare se stesso, anche se si sta esprimendo che il disegno nel più alto livello e possibili vie più astratta ...
... perché quelle astrazioni sono troppo difficili da progettare correttamente, e quando un progetto è difficile da correggere, e tutto dipende da esso, questa è una ricetta per i cambiamenti di progettazione centrale più costosi che si increspano e rompono tutto a seconda di esso. Quindi l'ultima cosa che vuoi è che le dipendenze nei tuoi sistemi scorrano verso progetti astratti troppo difficili da correggere (troppo difficile da stabilizzare senza cambiamenti intrusivi).
Difficile Dipende da Facile, Non Facile Dipende da Difficile
Quindi quello che facciamo invece è far fluire le dipendenze verso cose facili da progettare. È molto più facile progettare un "Modello" astratto che si concentra solo sulla memorizzazione di cose come poligoni e materiali e ottenere quel progetto corretto piuttosto che progettare un "Renderer" astratto che può essere effettivamente implementato (attraverso sottotipi concreti sostituibili) per servire il disegno richiede uniformemente hardware diverso da una PSP da un PC.
Quindi invertiamo le dipendenze dalle cose che sono difficili da progettare. Invece di far conoscere ai modelli astratti come attingere a un disegno di rendering astratto da cui dipendono tutti (e interrompere le loro implementazioni se tale progetto cambia), abbiamo invece un renderista astratto che sa come disegnare ogni oggetto astratto nella nostra scena ( modelli, emettitori di particelle, ecc.) e quindi possiamo implementare un sottotipo di rendering OpenGL per PC come RendererGl
, un altro per PSP come RendererPsp
, un altro per telefoni cellulari, ecc. In tal caso le dipendenze fluiscono verso progetti stabili, facili da correggere, dal renderer a vari tipi di entità (modelli, particelle, trame, ecc.) nella nostra scena, non viceversa.
- Sto usando "stabilità / instabilità" in un senso leggermente diverso dalla metrica degli accoppiamenti afferenti / efferenti di zio Bob che sta misurando più la difficoltà del cambiamento per quanto posso capire. Sto parlando di più sulla "probabilità di richiedere un cambiamento", sebbene la sua metrica di stabilità sia utile lì. Quando la "probabilità di cambiamento" è proporzionale alla "facilità di cambiamento" (es: le cose che hanno maggiori probabilità di richiedere cambiamenti hanno la massima instabilità e accoppiamenti afferenti dalla metrica dello zio Bob), allora tali cambiamenti probabili sono economici e non invadenti da rendere , che richiede solo la sostituzione di un'implementazione senza toccare alcun progetto centrale.
Se ti ritrovi a cercare di sottrarre qualcosa a livello centrale della tua base di codice ed è troppo difficile da progettare, invece di battere testardamente contro i muri e apportare costantemente modifiche invasive ogni mese / anno che richiedono l'aggiornamento di 8.000 file di origine perché è rompendo tutto ciò che dipende da esso, il mio suggerimento numero uno è di considerare di invertire le dipendenze. Vedi se riesci a scrivere il codice in modo tale che la cosa che è così difficile da progettare dipende da tutto ciò che è più facile da progettare, non avere le cose che sono più facili da progettare a seconda della cosa che è così difficile da progettare. Tieni presente che sto parlando di progetti (in particolare progetti di interfacce) e non di implementazioni: a volte le cose sono facili da progettare e difficili da implementare, e a volte le cose sono difficili da progettare ma facili da implementare. Le dipendenze scorrono verso i progetti, quindi l'attenzione dovrebbe essere solo su quanto sia difficile progettare qualcosa qui per determinare la direzione in cui scorrono le dipendenze.
Principio unico di responsabilità
Per me SRP non è così interessante qui di solito (anche se dipende dal contesto). Voglio dire, c'è un atto di bilanciamento sul filo del rasoio nel progettare cose che siano chiare nello scopo e mantenibili, ma i tuoi Shape
oggetti potrebbero dover esporre informazioni più dettagliate se non sanno come disegnare se stessi, per esempio, e potrebbero non esserci molte cose significative da fare con una forma in un contesto d'uso particolare che costruirla e disegnarla. Ci sono compromessi con quasi tutto, e non è correlato a SRP che può rendere le cose consapevoli di come trarre se stessi capaci di diventare un incubo di mantenimento nella mia esperienza in determinati contesti.
Ha molto più a che fare con l'accoppiamento e la direzione in cui fluiscono le dipendenze nel sistema. Se stai provando a eseguire il porting di un'interfaccia di rendering astratta da cui tutto dipende (perché la stanno usando per disegnare se stessi) su una nuova API / hardware di destinazione e ti rendi conto che devi cambiare notevolmente il suo design per farlo funzionare efficacemente lì, quindi è una modifica molto costosa da apportare che richiede la sostituzione di implementazioni di tutto ciò che nel tuo sistema sa come disegnare. E questo è il problema di manutenzione più pratico che incontro con le cose consapevoli di come disegnare se stessi che si traduce in un carico di dipendenze che scorre verso astrazioni che sono troppo difficili da progettare correttamente in anticipo.
Orgoglio degli sviluppatori
Cito questo punto perché, nella mia esperienza, questo è spesso il più grande ostacolo a indirizzare la direzione delle dipendenze verso cose più facili da progettare. È molto facile per gli sviluppatori diventare un po 'ambiziosi qui e dire: "Progetterò l'astrazione di rendering multipiattaforma per dominarli tutti, risolverò ciò che gli altri sviluppatori trascorrono mesi in porting e otterrò giusto e funzionerà come per magia su ogni singola piattaforma che supportiamo e utilizziamo le tecniche di rendering all'avanguardia su ognuno; l'ho già immaginato nella mia testa ".Nel qual caso resistono alla soluzione pratica che è quella di evitare di farlo e capovolgere la direzione delle dipendenze e tradurre ciò che potrebbe essere enormemente costoso e le ricorrenti modifiche alla progettazione centrale in semplici modifiche ricorrenti economiche e locali all'implementazione. C'è bisogno di una sorta di istinto di "bandiera bianca" negli sviluppatori per arrendersi quando qualcosa è troppo difficile da progettare a un livello così astratto e riconsiderare la loro intera strategia, altrimenti si trovano in un sacco di dolore e sofferenza. Suggerirei di trasferire tali ambizioni e spirito combattivo a implementazioni all'avanguardia di una cosa più semplice da progettare piuttosto che portare tali ambizioni che conquistano il mondo a livello di progettazione dell'interfaccia.