Come molte altre cose nello sviluppo del software, il grado di accoppiamento nei sistemi software è un compromesso tra obiettivi concorrenti.
Considera questo metodo di costruzione che accetta un Cliente come parametro.
public CustomerProcessor(Customer customer)
Come puoi vedere, sei strettamente legato alla classe Cliente. Puoi iniettarlo da qualche altra parte, ma sei limitato a quel tipo specifico.
Ora considera questo costruttore:
public CustomerProcessor(ICustomer customer)
Ora stiamo specificando un'interfaccia invece di una classe. Possiamo passare qualsiasi oggetto desideriamo a questo metodo di costruzione, purché tale oggetto sia conforme all'interfaccia ICustomer
(incluso un mock o stub, a scopo di test).
E se lo facessimo?
public CustomerProcessor(string customer)
Cosa c'è in customer
? Una stringa JSON? Una stringa XML? Solo un nome? Potrebbe essere qualsiasi cosa. Il modo in cui viene gestito dipende dal codice presente nell'oggetto CustomerProcessor
, ma se il tuo oggetto CustomerProcessor
è stato effettivamente passato in qualche altra classe come ICustomerProcessor
, potresti avere una famiglia di CustomerProcessors che potrebbe capire quale sia il formato e analizza correttamente la stringa automaticamente, inclusa una modifica da int
a int[]
. Ora sei ancora più sciolto.
Che cosa ti costa? Sicurezza del tipo in fase di compilazione, ovviamente. Quello, e significativamente aumentata la complessità. Ti costa anche le prestazioni.
Quindi, perché mai dovresti farlo? Bene, ci sono casi d'uso validi. Prendi in considerazione un sistema di sensori. Ogni sensore ha un valore o un insieme di valori che emette. Esistono centinaia di tipi di sensori diversi. Scriveresti un corso per ogni nuovo? Alcuni sistemi come questo passano semplicemente tali informazioni come stringhe lungo un bus dati. Ogni stringa ha un tipo di sensore incorporato, in modo che il sensore possa essere identificato correttamente e i suoi dati vengano decodificati correttamente dalla stringa.
Per quanto riguarda il tuo effetto a catena, la modifica di un membro di un tipo dovrebbe influire solo sul codice che effettivamente tocchi il tuo int
. Quando decidi di passare da int
a int[]
, ti impegni a refactoring di tutto il codice nel tuo sistema che tocca int
. Questo è by-design. Ha più a che fare con i sistemi di tipo che con il grado di accoppiamento tra i moduli software; dal momento che stai cambiando la tua dichiarazione fondamentale su cosa sia un cliente, il sistema di tipi si lamenterà quando il tuo vecchio codice non soddisfa le nuove aspettative.