Qual è il principio di inversione di dipendenza e perché è importante?
Qual è il principio di inversione di dipendenza e perché è importante?
Risposte:
Dai un'occhiata a questo documento: il principio di inversione di dipendenza .
In pratica dice:
Per quanto riguarda il motivo per cui è importante, in breve: le modifiche sono rischiose e, dipendendo da un concetto anziché da un'implementazione, si riduce la necessità di modifiche nei siti di chiamata.
In effetti, il DIP riduce l'accoppiamento tra diversi pezzi di codice. L'idea è che sebbene ci siano molti modi per implementare, diciamo, una funzione di registrazione, il modo in cui la useresti dovrebbe essere relativamente stabile nel tempo. Se riesci a estrarre un'interfaccia che rappresenta il concetto di registrazione, questa interfaccia dovrebbe essere molto più stabile nel tempo rispetto alla sua implementazione e i siti di chiamata dovrebbero essere molto meno influenzati dalle modifiche che potresti apportare mantenendo o estendendo quel meccanismo di registrazione.
Inoltre, facendo in modo che l'implementazione dipenda da un'interfaccia, avrai la possibilità di scegliere in fase di esecuzione quale implementazione sia più adatta al tuo particolare ambiente. A seconda dei casi, potrebbe essere interessante anche questo.
I libri Agile Software Development, Principles, Patterns and Practices e Agile Principles, Patterns and Practices in C # sono le migliori risorse per comprendere appieno gli obiettivi e le motivazioni originali alla base del principio di inversione di dipendenza. L'articolo "The Dependency Inversion Principle" è anche una buona risorsa, ma a causa del fatto che si tratta di una versione ridotta di una bozza che alla fine si è fatta strada nei libri precedentemente menzionati, lascia fuori alcune importanti discussioni sul concetto di un la proprietà del pacchetto e dell'interfaccia che sono fondamentali per distinguere questo principio dai consigli più generali di "programmare su un'interfaccia, non un'implementazione" che si trova nel libro Design Patterns (Gamma, et. al).
Per fornire un riepilogo, il principio di inversione di dipendenza riguarda principalmente l' inversione della direzione convenzionale delle dipendenze da componenti di "livello superiore" a componenti di "livello inferiore" in modo tale che i componenti di "livello inferiore" dipendono dalle interfacce di proprietà dei componenti di "livello superiore" . (Nota: il componente "livello superiore" qui si riferisce al componente che richiede dipendenze / servizi esterni, non necessariamente alla sua posizione concettuale all'interno di un'architettura a strati.) In tal modo, l'accoppiamento non si riduce tanto quanto viene spostato da componenti teoricamente meno prezioso per i componenti che sono teoricamente più preziosi.
Ciò si ottiene progettando componenti le cui dipendenze esterne sono espresse in termini di un'interfaccia per la quale un'implementazione deve essere fornita dal consumatore del componente. In altre parole, le interfacce definite esprimono ciò che è necessario al componente, non il modo in cui si utilizza il componente (ad es. "INeedSomething", non "IDoSomething").
Ciò a cui il principio di inversione di dipendenza non fa riferimento è la semplice pratica di astrarre dipendenze attraverso l'uso di interfacce (ad esempio MyService → [ILogger ⇐ Logger]). Mentre questo separa un componente dai dettagli di implementazione specifici della dipendenza, non inverte la relazione tra il consumatore e la dipendenza (ad esempio [MyService → IMyServiceLogger] ⇐ Logger.
L'importanza del principio di inversione di dipendenza può essere distillata fino a un unico obiettivo di poter riutilizzare i componenti software che si basano su dipendenze esterne per una parte della loro funzionalità (registrazione, convalida, ecc.)
All'interno di questo obiettivo generale di riutilizzo, possiamo delineare due sottotipi di riutilizzo:
Utilizzo di un componente software all'interno di più applicazioni con implementazioni di sub-dipendenza (ad esempio, è stato sviluppato un contenitore DI e si desidera fornire la registrazione, ma non si desidera associare il proprio contenitore a un logger specifico in modo tale che anche tutti coloro che usano il proprio contenitore devono utilizzare la libreria di registrazione scelta).
Utilizzo di componenti software in un contesto in evoluzione (ad esempio, sono stati sviluppati componenti di logica aziendale che rimangono gli stessi su più versioni di un'applicazione in cui i dettagli di implementazione stanno evolvendo).
Con il primo caso di riutilizzo di componenti tra più applicazioni, ad esempio con una libreria di infrastruttura, l'obiettivo è fornire un'esigenza di infrastruttura di base ai tuoi consumatori senza associare i tuoi consumatori a sotto-dipendenze della tua biblioteca poiché prendere dipendenze da tali dipendenze richiede anche i consumatori richiedono le stesse dipendenze. Ciò può essere problematico quando gli utenti della tua biblioteca scelgono di utilizzare una libreria diversa per le stesse esigenze di infrastruttura (ad esempio NLog vs. log4net) o se scelgono di utilizzare una versione successiva della libreria richiesta che non è compatibile con la versione precedente richiesto dalla tua biblioteca.
Con il secondo caso di riutilizzo di componenti di logica aziendale (ovvero "componenti di livello superiore"), l'obiettivo è quello di isolare l'implementazione del dominio principale dell'applicazione dalle mutevoli esigenze dei dettagli dell'implementazione (ovvero modifica / aggiornamento delle librerie di persistenza, librerie di messaggistica , strategie di crittografia, ecc.). Idealmente, la modifica dei dettagli di implementazione di un'applicazione non dovrebbe interrompere i componenti che incapsulano la logica aziendale dell'applicazione.
Nota: alcuni potrebbero obiettare a descrivere questo secondo caso come un reale riutilizzo, ragionando sul fatto che componenti come i componenti della logica aziendale utilizzati all'interno di una singola applicazione in evoluzione rappresentano solo un singolo utilizzo. L'idea qui, tuttavia, è che ogni modifica ai dettagli di implementazione dell'applicazione rende un nuovo contesto e quindi un caso d'uso diverso, sebbene gli obiettivi finali possano essere distinti come isolamento vs. portabilità.
Mentre seguire il principio di inversione di dipendenza in questo secondo caso può offrire qualche vantaggio, va notato che il suo valore applicato ai linguaggi moderni come Java e C # è molto ridotto, forse al punto da essere irrilevante. Come discusso in precedenza, il DIP implica la separazione completa dei dettagli di implementazione in pacchetti separati. Nel caso di un'applicazione in evoluzione, tuttavia, il semplice utilizzo di interfacce definite in termini di dominio aziendale eviterà la necessità di modificare componenti di livello superiore a causa delle mutate esigenze dei componenti dei dettagli di implementazione, anche se i dettagli di implementazione risiedono in definitiva nello stesso pacchetto . Questa parte del principio riflette aspetti che erano pertinenti alla lingua in vista quando il principio era codificato (cioè C ++) che non sono rilevanti per le nuove lingue. Detto ciò,
Una discussione più lunga di questo principio in relazione al semplice uso di interfacce, Iniezione delle dipendenze e modello di interfaccia separata è disponibile qui . Inoltre, una discussione su come il principio si riferisce a linguaggi tipicamente dinamici come JavaScript può essere trovato qui .
Quando progettiamo applicazioni software possiamo considerare le classi di basso livello le classi che implementano operazioni di base e primarie (accesso al disco, protocolli di rete, ...) e le classi di alto livello le classi che incapsulano la logica complessa (flussi di business, ...).
Gli ultimi si basano su classi di basso livello. Un modo naturale di implementare tali strutture sarebbe quello di scrivere classi di basso livello e una volta che le abbiamo per scrivere le classi complesse di alto livello. Poiché le classi di alto livello sono definite in termini di altre, questo sembra il modo logico per farlo. Ma questo non è un design flessibile. Cosa succede se dobbiamo sostituire una classe di basso livello?
Il principio di inversione di dipendenza afferma che:
Questo principio cerca di "invertire" l'idea convenzionale secondo cui i moduli di alto livello nel software dovrebbero dipendere dai moduli di livello inferiore. Qui i moduli di alto livello possiedono l'astrazione (ad esempio, decidendo i metodi dell'interfaccia) che sono implementati dai moduli di livello inferiore. In questo modo i moduli di livello inferiore dipendono dai moduli di livello superiore.
L'inversione delle dipendenze ben applicata offre flessibilità e stabilità a livello dell'intera architettura dell'applicazione. Permetterà all'applicazione di evolversi in modo più sicuro e stabile.
Tradizionalmente un'interfaccia utente di architettura a più livelli dipendeva dal livello aziendale e questo a sua volta dipendeva dal livello di accesso ai dati.
Devi comprendere livello, pacchetto o libreria. Vediamo come sarebbe il codice.
Avremmo una libreria o un pacchetto per il livello di accesso ai dati.
// DataAccessLayer.dll
public class ProductDAO {
}
E un'altra logica aziendale di libreria o livello pacchetto che dipende dal livello di accesso ai dati.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private ProductDAO productDAO;
}
L'inversione di dipendenza indica quanto segue:
I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.
Quali sono i moduli di alto livello e basso livello? Moduli pensanti come librerie o pacchetti, modulo di alto livello sarebbero quelli che tradizionalmente hanno dipendenze e basso livello da cui dipendono.
In altre parole, il livello alto del modulo è dove viene invocata l'azione e il livello basso dove viene eseguita l'azione.
Una conclusione ragionevole da trarre da questo principio è che non dovrebbe esserci dipendenza tra concrezioni, ma deve esserci dipendenza da un'astrazione. Ma secondo l'approccio che adottiamo possiamo applicare erroneamente la dipendenza dagli investimenti, ma un'astrazione.
Immagina di adattare il nostro codice come segue:
Avremmo una libreria o un pacchetto per il livello di accesso ai dati che definiscono l'astrazione.
// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{
}
E un'altra logica aziendale di libreria o livello pacchetto che dipende dal livello di accesso ai dati.
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private IProductDAO productDAO;
}
Sebbene dipendiamo da una dipendenza di astrazione tra business e accesso ai dati rimane lo stesso.
Per ottenere l'inversione di dipendenza, l'interfaccia di persistenza deve essere definita nel modulo o nel pacchetto in cui si trova questa logica o dominio di alto livello e non nel modulo di basso livello.
Definisci innanzitutto qual è il livello di dominio e l'astrazione della sua comunicazione è definita persistenza.
// Domain.dll
public interface IProductRepository;
using DataAccessLayer;
public class ProductBO {
private IProductRepository productRepository;
}
Dopo che il livello di persistenza dipende dal dominio, ora è possibile invertire se viene definita una dipendenza.
// Persistence.dll
public class ProductDAO : IProductRepository{
}
(fonte: xurxodev.com )
È importante assimilare bene il concetto, approfondendo lo scopo e i benefici. Se rimaniamo meccanicamente e apprendiamo il tipico repository di casi, non saremo in grado di identificare dove possiamo applicare il principio di dipendenza.
Ma perché invertire una dipendenza? Qual è l'obiettivo principale al di là di esempi specifici?
Questo comunemente consente alle cose più stabili, che non dipendono da cose meno stabili, di cambiare più frequentemente.
È più facile modificare il tipo di persistenza, sia il database che la tecnologia accedono allo stesso database rispetto alla logica del dominio o alle azioni progettate per comunicare con persistenza. Per questo motivo, la dipendenza viene invertita perché poiché è più facile cambiare la persistenza se si verifica questo cambiamento. In questo modo non dovremo cambiare il dominio. Il livello di dominio è il più stabile di tutti, motivo per cui non dovrebbe dipendere da nulla.
Ma non esiste solo questo esempio di repository. Ci sono molti scenari in cui si applica questo principio e ci sono architetture basate su questo principio.
Ci sono architetture in cui l'inversione di dipendenza è la chiave della sua definizione. In tutti i domini è il più importante ed è l'astrazione che indicherà che il protocollo di comunicazione tra il dominio e il resto dei pacchetti o delle librerie sono definiti.
Nell'architettura pulita il dominio si trova al centro e se si guarda nella direzione delle frecce che indicano la dipendenza, è chiaro quali sono i livelli più importanti e stabili. Gli strati esterni sono considerati strumenti instabili, quindi evitare di dipenderli.
(fonte: 8thlight.com )
Succede allo stesso modo con l'architettura esagonale, in cui il dominio si trova anche nella parte centrale e le porte sono astrazioni di comunicazione dal domino verso l'esterno. Anche in questo caso è evidente che il dominio è la più stabile e la dipendenza tradizionale è invertita.
Per me, il principio di inversione di dipendenza, come descritto nell'articolo ufficiale , è in realtà un tentativo fuorviato di aumentare la riusabilità dei moduli intrinsecamente meno riutilizzabili, nonché un modo per aggirare un problema nel linguaggio C ++.
Il problema in C ++ è che i file header in genere contengono dichiarazioni di campi e metodi privati. Pertanto, se un modulo C ++ di alto livello include il file di intestazione per un modulo di basso livello, dipenderà dai dettagli di implementazione effettivi di quel modulo. E questo, ovviamente, non è una buona cosa. Ma questo non è un problema nelle lingue più moderne comunemente usate oggi.
I moduli di alto livello sono intrinsecamente meno riutilizzabili rispetto ai moduli di basso livello perché i primi sono normalmente più specifici per applicazione / contesto rispetto ai secondi. Ad esempio, un componente che implementa una schermata dell'interfaccia utente è di altissimo livello e anche molto (completamente?) Specifico per l'applicazione. Cercare di riutilizzare un componente del genere in un'applicazione diversa è controproducente e può solo portare a un eccesso di ingegneria.
Quindi, la creazione di un'astrazione separata allo stesso livello di un componente A che dipende da un componente B (che non dipende da A) può essere fatta solo se il componente A sarà davvero utile per il riutilizzo in diverse applicazioni o contesti. In caso contrario, l'applicazione di DIP sarebbe una cattiva progettazione.
Fondamentalmente dice:
La classe dovrebbe dipendere da astrazioni (ad es. Interfaccia, classi astratte), non da dettagli specifici (implementazioni).
Buone risposte e buoni esempi sono già stati dati da altri qui.
Il motivo per cui DIP è importante è perché garantisce il principio OO "design debolmente accoppiato".
Gli oggetti nel software NON devono entrare in una gerarchia in cui alcuni oggetti sono quelli di livello superiore, a seconda degli oggetti di livello inferiore. Le modifiche agli oggetti di basso livello verranno quindi riordinate agli oggetti di livello superiore, il che rende il software molto fragile per il cambiamento.
Volete che i vostri oggetti di "livello superiore" siano molto stabili e non fragili per il cambiamento, quindi è necessario invertire le dipendenze.
Un modo molto più chiaro per affermare il principio di inversione di dipendenza è:
I moduli che incapsulano la logica aziendale complessa non dovrebbero dipendere direttamente da altri moduli che incapsulano la logica aziendale. Invece, dovrebbero dipendere solo dalle interfacce per i dati semplici.
Cioè, invece di implementare la tua classe Logic
come fanno le persone di solito:
class Dependency { ... }
class Logic {
private Dependency dep;
int doSomething() {
// Business logic using dep here
}
}
dovresti fare qualcosa del tipo:
class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
private Dependency dep;
...
}
class Logic {
int doSomething(Data data) {
// compute something with data
}
}
Data
e DataFromDependency
dovrebbe vivere nello stesso modulo di Logic
, non con Dependency
.
Perché farlo
Dependency
cambia, non è necessario cambiare Logic
.Logic
fa è un compito molto più semplice: funziona solo su quello che sembra un ADT.Logic
ora può essere testato più facilmente. Ora puoi creare un'istanza direttamente Data
con dati falsi e trasferirli. Non sono necessarie simulazioni o impalcature di prova complesse.DataFromDependency
, che fa direttamente riferimento Dependency
, si trova nello stesso modulo di Logic
, allora il Logic
modulo dipende ancora direttamente dal Dependency
modulo al momento della compilazione. Per lo zio Bob la spiegazione del principio , evitando che questo sia il punto centrale di DIP. Piuttosto, per seguire DIP, Data
dovrebbe essere nello stesso modulo di Logic
, ma DataFromDependency
dovrebbe essere nello stesso modulo di Dependency
.
Inversion of control (IoC) è un modello di progettazione in cui un oggetto ottiene la sua dipendenza da un framework esterno, piuttosto che chiedere a un framework la sua dipendenza.
Esempio di pseudocodice utilizzando la ricerca tradizionale:
class Service {
Database database;
init() {
database = FrameworkSingleton.getService("database");
}
}
Codice simile usando IoC:
class Service {
Database database;
init(database) {
this.database = database;
}
}
I vantaggi di IoC sono:
Il punto di inversione di dipendenza è rendere software riutilizzabile.
L'idea è che invece di due parti di codice che si basano l'una sull'altra, si basano su un'interfaccia astratta. Quindi puoi riutilizzare uno dei due pezzi senza l'altro.
Il modo più comunemente raggiunto è attraverso un'inversione del contenitore di controllo (IoC) come Spring in Java. In questo modello, le proprietà degli oggetti sono impostate tramite una configurazione XML invece che gli oggetti escono e trovano la loro dipendenza.
Immagina questo pseudocodice ...
public class MyClass
{
public Service myService = ServiceLocator.service;
}
MyClass dipende direttamente sia dalla classe Service che dalla classe ServiceLocator. Ha bisogno di entrambi se si desidera utilizzarlo in un'altra applicazione. Ora immagina questo ...
public class MyClass
{
public IService myService;
}
Ora, MyClass si basa su una singola interfaccia, l'interfaccia IService. Permetteremmo al contenitore IoC di impostare effettivamente il valore di quella variabile.
Quindi ora MyClass può essere facilmente riutilizzato in altri progetti, senza portare con sé la dipendenza di quelle altre due classi.
Ancora meglio, non devi trascinare le dipendenze di MyService, le dipendenze di quelle dipendenze e ... beh, hai capito.
Se possiamo dare per scontato che un dipendente di "alto livello" in una società è pagato per l'esecuzione dei suoi piani e che questi piani sono forniti dall'esecuzione aggregata di molti piani di dipendenti di "basso livello", allora potremmo dire è generalmente un piano terribile se la descrizione del piano del dipendente di alto livello è in qualche modo accoppiata al piano specifico di qualsiasi dipendente di livello inferiore.
Se un dirigente di alto livello ha un piano per "migliorare i tempi di consegna" e indica che un dipendente della linea di spedizione deve prendere un caffè e fare stretching ogni mattina, quel piano è altamente accoppiato e ha una bassa coesione. Ma se il piano non menziona alcun dipendente specifico e in realtà richiede semplicemente "un'entità in grado di svolgere il lavoro è pronta a lavorare", allora il piano è liberamente accoppiato e più coerente: i piani non si sovrappongono e possono essere facilmente sostituiti . Gli appaltatori o robot possono facilmente sostituire i dipendenti e il piano di alto livello rimane invariato.
"Livello elevato" nel principio di inversione di dipendenza significa "più importante".
Vedo che una buona spiegazione è stata data nelle risposte sopra. Tuttavia, voglio fornire alcune semplici spiegazioni con un semplice esempio.
Il principio di inversione di dipendenza consente al programmatore di rimuovere le dipendenze codificate in modo tale che l'applicazione diventi liberamente accoppiabile ed estendibile.
Come raggiungere questo obiettivo: attraverso l'astrazione
Senza inversione di dipendenza:
class Student {
private Address address;
public Student() {
this.address = new Address();
}
}
class Address{
private String perminentAddress;
private String currentAdrress;
public Address() {
}
}
Nello snippet di codice precedente, l'oggetto address è hardcoded. Invece se possiamo usare l'inversione di dipendenza e iniettare l'oggetto indirizzo passando attraverso il metodo di costruzione o setter. Vediamo.
Con inversione di dipendenza:
class Student{
private Address address;
public Student(Address address) {
this.address = address;
}
//or
public void setAddress(Address address) {
this.address = address;
}
}
Inversione di dipendenza: dipende dalle astrazioni, non dalle concrezioni.
Inversione del controllo: principale vs astrazione e come il principale è la colla dei sistemi.
Questi sono alcuni buoni post che parlano di questo:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
Diciamo che abbiamo due classi: Engineer
e Programmer
:
L'ingegnere di classe dipende dalla classe del programmatore, come di seguito:
class Engineer () {
fun startWork(programmer: Programmer){
programmer.work()
}
}
class Programmer {
fun work(){
//TODO Do some work here!
}
}
In questo esempio la classe Engineer
ha una dipendenza dalla nostra Programmer
classe. Cosa succederà se devo cambiare il Programmer
?
Ovviamente anche io devo cambiare Engineer
. (Caspita, anche a questo punto OCP
viene violato)
Quindi, cosa dobbiamo ripulire questo casino? La risposta è in realtà l'astrazione. Per astrazione, possiamo rimuovere la dipendenza tra queste due classi. Ad esempio, posso creare un Interface
per la classe Programmer e da ora in poi ogni classe che vuole usare Programmer
deve usarlaInterface
, quindi cambiando la classe Programmer, non abbiamo bisogno di cambiare alcuna classe che l'ha usata, a causa dell'astrazione che abbiamo Usato.
Nota: DependencyInjection
ci può aiutare a fare DIP
e SRP
troppo.
Aggiungendo alla raffica di risposte generalmente buone, vorrei aggiungere un mio piccolo campione per dimostrare le buone e le cattive pratiche. E sì, non sono uno che lancia pietre!
Supponiamo che tu voglia un piccolo programma per convertire una stringa in formato base64 tramite l'I / O della console. Ecco l'approccio ingenuo:
class Program
{
static void Main(string[] args)
{
/*
* BadEncoder: High-level class *contains* low-level I/O functionality.
* Hence, you'll have to fiddle with BadEncoder whenever you want to change
* the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
* problems with I/O shouldn't break the encoder!
*/
BadEncoder.Run();
}
}
public static class BadEncoder
{
public static void Run()
{
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
}
}
Il DIP afferma sostanzialmente che i componenti di alto livello non dovrebbero dipendere dall'implementazione di basso livello, dove "livello" è la distanza dall'I / O secondo Robert C. Martin ("Clean Architecture"). Ma come uscire da questa situazione? Semplicemente rendendo l'encoder centrale dipendente solo dalle interfacce senza disturbare il modo in cui sono implementate:
class Program
{
static void Main(string[] args)
{
/* Demo of the Dependency Inversion Principle (= "High-level functionality
* should not depend upon low-level implementations"):
* You can easily implement new I/O methods like
* ConsoleReader, ConsoleWriter without ever touching the high-level
* Encoder class!!!
*/
GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter()); }
}
public static class GoodEncoder
{
public static void Run(IReadable input, IWriteable output)
{
output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
}
}
public interface IReadable
{
string ReadInput();
}
public interface IWriteable
{
void WriteOutput(string txt);
}
public class ConsoleReader : IReadable
{
public string ReadInput()
{
return Console.ReadLine();
}
}
public class ConsoleWriter : IWriteable
{
public void WriteOutput(string txt)
{
Console.WriteLine(txt);
}
}
Nota che non è necessario toccare GoodEncoder
per cambiare la modalità I / O: quella classe è soddisfatta delle interfacce I / O che conosce; qualsiasi implementazione di basso livello IReadable
e IWriteable
non la disturberà mai.
GoodEncoder
tuo secondo esempio. Per creare un esempio DIP, è necessario introdurre una nozione di ciò che "possiede" le interfacce che hai estratto qui - e, in particolare, metterle nello stesso pacchetto di GoodEncoder mentre le loro implementazioni rimangono all'esterno.
Il principio di inversione di dipendenza (DIP) afferma che
i) I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
ii) Le astrazioni non dovrebbero mai dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.
Esempio:
public interface ICustomer
{
string GetCustomerNameById(int id);
}
public class Customer : ICustomer
{
//ctor
public Customer(){}
public string GetCustomerNameById(int id)
{
return "Dummy Customer Name";
}
}
public class CustomerFactory
{
public static ICustomer GetCustomerData()
{
return new Customer();
}
}
public class CustomerBLL
{
ICustomer _customer;
public CustomerBLL()
{
_customer = CustomerFactory.GetCustomerData();
}
public string GetCustomerNameById(int id)
{
return _customer.GetCustomerNameById(id);
}
}
public class Program
{
static void Main()
{
CustomerBLL customerBLL = new CustomerBLL();
int customerId = 25;
string customerName = customerBLL.GetCustomerNameById(customerId);
Console.WriteLine(customerName);
Console.ReadKey();
}
}
Nota: la classe dovrebbe dipendere da astrazioni come interfaccia o classi astratte, non da dettagli specifici (implementazione dell'interfaccia).