Нека създадем фиктивен пример за два класа A
и B
.
Клас A
в пакет packageA
:
package packageA;
import packageB.B;
public class A {
private B myB;
public A() {
this.myB = new B();
}
public void doSomethingThatUsesB() {
System.out.println("Doing things with myB");
this.myB.doSomething();
}
}
Клас B
в пакет packageB
:
package packageB;
public class B {
public void doSomething() {
System.out.println("B did something.");
}
}
Както виждаме, A
зависи от B
. Без B
, A
не може да се използва. Казваме, че A
е тясно свързано с B
. Ами ако искаме да заменим B
в бъдеще с BetterB
? За целта създаваме интерфейс Inter
в рамките на packageA
:
package packageA;
public interface Inter {
public void doSomething();
}
За да използваме този интерфейс, ние
import packageA.Inter;
и нека B implements Inter
в B
и
- Заменете всички срещания на
B
в рамките на A
с Inter
.
Резултатът е тази модифицирана версия на A
:
package packageA;
public class A {
private Inter myInter;
public A() {
this.myInter = ???; // What to do here?
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
Вече можем да видим, че зависимостта от A
до B
е изчезнала: import packageB.B;
вече не е необходим. Има само един проблем: не можем да създадем екземпляр на интерфейс. Но Инверсията на управление идва на помощ: вместо да инстанцира нещо от тип Inter
в рамките на конструктора на A
, конструкторът ще изисква нещо, което implements Inter
като параметър:
package packageA;
public class A {
private Inter myInter;
public A(Inter myInter) {
this.myInter = myInter;
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
С този подход вече можем да променим конкретната реализация на Inter
в рамките на A
по желание. Да предположим, че напишем нов клас BetterB
:
package packageB;
import packageA.Inter;
public class BetterB implements Inter {
@Override
public void doSomething() {
System.out.println("BetterB did something.");
}
}
Сега можем да инстантираме A
s с различни Inter
-имплементации:
Inter b = new B();
A aWithB = new A(b);
aWithB.doSomethingThatUsesInter();
Inter betterB = new BetterB();
A aWithBetterB = new A(betterB);
aWithBetterB.doSomethingThatUsesInter();
И не трябваше да променяме нищо в рамките на A
. Кодът вече е отделен и можем да променим конкретната реализация на Inter
по желание, стига договорът(ите) на Inter
да е(са) удовлетворен(и). Най-вече можем да поддържаме код, който ще бъде написан в бъдеще и ще прилага Inter
.
Допълнение
Написах този отговор през 2015 г. Въпреки че като цяло бях доволен от отговора, винаги съм мислил, че нещо липсва и мисля, че най-накрая знам какво е то. Следното не е необходимо за разбиране на отговора, но има за цел да предизвика интерес у читателя, както и да предостави някои ресурси за по-нататъшно самообразование.
В литературата този подход е известен като Принцип на разделяне на интерфейса и принадлежи към SOLIDпринципи. Има хубав разговор от чичо Боб в YouTube (интересното е с дължина около 15 минути) показвайки как полиморфизмът и интерфейсите могат да бъдат използвани, за да позволят на зависимостта по време на компилиране да сочи срещу потока на контрол (препоръчително е преценката на зрителя, чичо Боб ще се изрече леко за Java). Това, в замяна, означава, че изпълнението на високо ниво не трябва да знае за изпълнението на по-ниско ниво, когато те са отделени чрез интерфейси. Така по-ниските нива могат да се разменят по желание, както показахме по-горе.
person
Turing85
schedule
11.06.2015