Cerchiamo di creare un esempio fittizio.
Classe A
nel pacchetto packageA
:
package packageA;
import packageB.B;
public class A {
B myB;
public A() {
this.myB = new B();
}
public void doSomethingThatUsesB() {
System.out.println("Doing things with myB");
this.myB.doSomething();
}
}
Classe B
nel pacchetto packageB
:
package packageB;
public class B {
public void doSomething() {
System.out.println("B did something.");
}
}
Come si vede, A
dipende B
. Senza B
, non è possibile utilizzare A
. Ma cosa, se vogliamo sostituire B
in futuro con un BetterB
? Ora, iniziamo a creare un'interfaccia Inter
entro packageA
:
package packageA;
public interface Inter {
public void doSomething();
}
Per utilizzare questa interfaccia, abbiamo import packageA.Inter;
e lasciare B implements Inter
in B
e sostituire tutte le occorrenze di B
all'interno A
con Inter
. Il risultato è questa versione modificata del A
:
package packageA;
public class A {
Inter myInter;
public A() {
this.myInter = ???; // What to do here?
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
A questo punto, si vede già che la dipendenza A
-B
è andato: la import packageB.B;
non è più necessario. C'è solo un problema: non possiamo istanziare un'istanza di un'interfaccia. Ma Inversion of control viene in soccorso. Invece di istanziare qualcosa di tipo Inter
wihtin costruttore A
s', noi chiederemo qualcosa che implements Inter
come parametro per il costruttore:
package packageA;
public class A {
Inter myInter;
public A(Inter myInter) {
this.myInter = myInter;
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
Con questo approccio possiamo ora modificare l'implementazione concreta di Inter
all'interno A
a volontà. Supponiamo di scrivere una nuova classe di BetterB
:
package packageB;
import packageA.Inter;
public class BetterB implements Inter {
@Override
public void doSomething() {
System.out.println("BetterB did something.");
}
}
Ora possiamo instantiante A
s con diverse implementazioni Inter
:
Inter b = new B();
A aWithB = new A(b);
aWithB.doSomethingThatUsesInter();
Inter betterB = new BetterB();
A aWithBetterB = new A(betterB);
aWithBetterB.doSomethingThatUsesInter();
e non abbiamo dovuto cambiare nulla all'interno A
. Il codice è ora disaccoppiato ed è possibile modificare l'implementazione concreta di Inter
a piacere, purché il (i) contratto (i) di Inter
sia (sono) soddisfatto. In particolare, è possibile supportare il codice, che verrà generato in futuro e implementa Inter
.
Consiglio vivamente le pubblicazioni di Robert C. Martin sulle regole SOLID della programmazione orientata agli oggetti. Ecco alcuni dettagli sul tuo problema: http://www.objectmentor.com/resources/articles/dip.pdf –