Per me è un problema di accoppiamento e legato alla granularità del design. Perfino la forma più libera di accoppiamento introduce dipendenze da una cosa all'altra. Se ciò viene fatto per centinaia o migliaia di oggetti, anche se sono tutti relativamente semplici, aderiscono a SRP e anche se tutte le dipendenze scorrono verso astrazioni stabili, ciò produce una base di codice che è molto difficile ragionare su come un tutto interrelato.
Ci sono cose pratiche che ti aiutano a valutare la complessità di una base di codice, non frequentemente discussa nella SE teorica, come solo quanto in profondità nello stack di chiamate puoi ottenere prima di raggiungere la fine e quanto in profondità devi andare prima che puoi, con molta fiducia, comprendere tutti i possibili effetti collaterali che potrebbero verificarsi a quel livello dello stack di chiamate, anche in caso di eccezione.
E ho scoperto, proprio nella mia esperienza, che i sistemi più piatti con stack di chiamate più superficiali tendono ad essere molto più facili da ragionare. Un esempio estremo sarebbe un sistema a componenti di entità in cui i componenti sono solo dati non elaborati. Solo i sistemi hanno funzionalità e, nel processo di implementazione e utilizzo di un ECS, l'ho trovato di gran lunga il sistema più semplice di sempre a ragionare su quando complesse basi di codice che si estendono su centinaia di migliaia di righe di codice praticamente si riducono a poche decine di sistemi che contiene tutte le funzionalità.
Troppe cose forniscono funzionalità
L'alternativa prima di quando lavoravo in codebase precedenti era un sistema con centinaia o migliaia di oggetti per lo più piccoli rispetto a qualche dozzina di sistemi ingombranti con alcuni oggetti usati solo per passare messaggi da un oggetto a un altro ( Message
oggetto, ad esempio, che aveva il suo propria interfaccia pubblica). Questo è fondamentalmente ciò che ottieni analogicamente quando ripristini l'ECS a un punto in cui i componenti hanno funzionalità e ogni combinazione unica di componenti in un'entità produce il proprio tipo di oggetto. E ciò tenderà a produrre funzioni più piccole e più semplici ereditate e fornite da infinite combinazioni di oggetti che modellano idee adolescenti ( Particle
oggetto vs.Physics System
, per esempio). Tuttavia, tende anche a produrre un grafico complesso di interdipendenze che rende difficile ragionare su ciò che accade a livello generale, semplicemente perché ci sono così tante cose nella base di codice che possono effettivamente fare qualcosa e quindi possono fare qualcosa di sbagliato - - tipi che non sono tipi di "dati", ma tipi di "oggetti" con funzionalità associate. I tipi che fungono da dati puri senza funzionalità associata non possono andare storti poiché non possono fare nulla da soli.
Le interfacce pure non aiutano molto questo problema di comprensibilità perché anche se ciò rende le "dipendenze in fase di compilazione" meno complicate e offre più spazio per il cambiamento e l'espansione, non rende le "dipendenze di runtime" e le interazioni meno complicate. L'oggetto client finisce comunque per invocare funzioni su un oggetto account concreto anche se vengono richiamati IAccount
. Il polimorfismo e le interfacce astratte hanno i loro usi ma non disaccoppiano le cose nel modo che ti aiuta davvero a ragionare su tutti gli effetti collaterali che potrebbero verificarsi in un dato punto. Per ottenere questo tipo di disaccoppiamento efficace, è necessario un codebase che abbia molte meno cose che contengono funzionalità.
Più dati, meno funzionalità
Quindi ho trovato l'approccio ECS, anche se non lo si applica completamente, per essere estremamente utile, poiché trasforma quelli che sarebbero stati centinaia di oggetti in soli dati grezzi con sistemi ingombranti, progettati in modo più grossolano, che forniscono tutti i funzionalità. Massimizza il numero di tipi di "dati" e minimizza il numero di tipi di "oggetti", quindi minimizza assolutamente il numero di posizioni nel sistema che possono effettivamente andare storte. Il risultato finale è un sistema molto "piatto" senza un grafico complesso di dipendenze, solo sistemi per componenti, mai viceversa e mai componenti per altri componenti. Sono sostanzialmente molti più dati grezzi e molte meno astrazioni che hanno l'effetto di centralizzare e appiattire la funzionalità della base di codice in aree chiave, astrazioni chiave.
30 cose più semplici non sono necessariamente più semplici da ragionare su 1 cosa più complessa, se quelle 30 cose più semplici sono correlate mentre la cosa complessa si trova da sola. Quindi il mio suggerimento è in realtà di trasferire la complessità lontano dalle interazioni tra oggetti e altro verso oggetti più voluminosi che non devono interagire con nient'altro per ottenere il disaccoppiamento di massa, verso interi "sistemi" (non monoliti e oggetti divini, intendiamoci, e non classi con 200 metodi, ma qualcosa di molto più alto livello di a Message
o Particle
a nonostante abbia un'interfaccia minimalista). E preferisci tipi di dati vecchi più semplici. Più dipendi da quelli, meno accoppiamento otterrai. Anche se questo contraddice alcune idee SE, ho scoperto che aiuta davvero molto.