SOLID設(shè)計(jì)原則依賴(lài)倒置原則
提供復(fù)雜邏輯的高級(jí)模塊應(yīng)易于重用,并且不受提供實(shí)用程序功能的低級(jí)模塊的更改的影響。
依賴(lài)倒置原則的定義
該原理的基本思想很簡(jiǎn)單,即很重要:提供復(fù)雜邏輯的高級(jí)模塊應(yīng)易于重用,并且不受提供實(shí)用程序功能的低級(jí)模塊的更改的影響。為此,您需要引入一個(gè)抽象,該抽象將高級(jí)模塊和低級(jí)模塊彼此分離。
基于此思想,Robert C. Martin對(duì)依賴(lài)倒置原則的定義包括兩個(gè)部分:
高級(jí)模塊不應(yīng)依賴(lài)于低級(jí)模塊。兩者都應(yīng)依賴(lài)抽象。
抽象不應(yīng)依賴(lài)細(xì)節(jié)。細(xì)節(jié)應(yīng)取決于抽象。
該定義的一個(gè)重要細(xì)節(jié)是,高層和低層模塊取決于抽象。設(shè)計(jì)原理不僅會(huì)改變依賴(lài)關(guān)系的方向,就像您第一次閱讀依賴(lài)關(guān)系的名稱(chēng)時(shí)所期望的那樣。通過(guò)在高級(jí)模塊和低級(jí)模塊之間引入抽象,它在高級(jí)模塊和低級(jí)模塊之間劃分了依賴(lài)關(guān)系。因此,最后,您得到兩個(gè)依賴(lài)項(xiàng):
高級(jí)模塊取決于抽象,并且低層依賴(lài)于相同的抽象。
基于其他SOLID原則
這聽(tīng)起來(lái)可能比通常要復(fù)雜得多。因此,如果您在代碼中應(yīng)用了“打開(kāi)/關(guān)閉原則”和“ Liskov替換原則”,則它也將遵循“依賴(lài)倒置原則”。
打開(kāi)/關(guān)閉原則要求打開(kāi)軟件組件以進(jìn)行擴(kuò)展,但關(guān)閉軟件組件以進(jìn)行修改。您可以通過(guò)引入可以提供不同實(shí)現(xiàn)的接口來(lái)實(shí)現(xiàn)這一點(diǎn)。接口本身已關(guān)閉以進(jìn)行修改,您可以通過(guò)提供新的接口實(shí)現(xiàn)輕松地對(duì)其進(jìn)行擴(kuò)展。
您的實(shí)現(xiàn)應(yīng)遵循Liskov替換原理,以便您可以在不破壞應(yīng)用程序的情況下將它們替換為同一接口的其他實(shí)現(xiàn)。
讓我們看一下CoffeeMachine項(xiàng)目,我將在其中應(yīng)用所有這三個(gè)設(shè)計(jì)原理。
用依賴(lài)倒置原理沖泡咖啡
您可以購(gòu)買(mǎi)許多不同的咖啡機(jī)。較為簡(jiǎn)單的是使用水和磨碎的咖啡來(lái)沖泡過(guò)濾咖啡,而高級(jí)的則包括用研磨機(jī)新鮮研磨所需量的咖啡豆,并且可以用來(lái)沖泡不同種類(lèi)的咖啡。
如果您構(gòu)建了一個(gè)咖啡機(jī)應(yīng)用程序,該應(yīng)用程序會(huì)在早上自動(dòng)為您沖泡一杯新鮮的咖啡,則可以將這些咖啡機(jī)建模為BasicCoffeeMachine和PremiumCoffeeMachine類(lèi)。
依賴(lài)反轉(zhuǎn)原理與代碼示例
實(shí)施BasicCoffeeMachine
BasicCoffeeMachine的實(shí)現(xiàn)非常簡(jiǎn)單。它僅實(shí)現(xiàn)一個(gè)構(gòu)造函數(shù)和兩個(gè)公共方法。您可以調(diào)用addGroundCoffee方法來(lái)重新填充研磨咖啡,并調(diào)用brewFilterCoffee方法來(lái)沖泡一杯過(guò)濾咖啡。

import java.util.Map;public class BasicCoffeeMachine implements CoffeeMachine {
private Configuration config;
private Map<CoffeeSelection, GroundCoffee> groundCoffee;
private BrewingUnit brewingUnit;
public BasicCoffeeMachine(Map<CoffeeSelection, GroundCoffee> coffee).
this.groundCoffee = coffee;
this.brewingUnit = new BrewingUnit();
this.config = new Configuration(30, 480);
}
@Override
public Coffee brewFilterCoffee() {
// get the coffee
GroundCoffee groundCoffee = this.groundCoffee.get(CoffeeSelection.FILTER_COFFEE);
// brew a filter coffee
return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE, groundCoffee, this.config.getQuantityWater());
}
public void addGroundCoffee(CoffeeSelection sel, GroundCoffee newCoffee) throws CoffeeException {
GroundCoffee existingCoffee = this.groundCoffee.get(sel);
if (existingCoffee != null) {
if (existingCoffee.getName().equals(newCoffee.getName())) {
existingCoffee.setQuantity(existingCoffee.getQuantity() + newCoffee.getQuantity())
} else {
throw new CoffeeException("Only one kind of coffee supported for each CoffeeSelection.")
}
} else {
this.groundCoffee.put(sel, newCoffee)
}
} }要實(shí)現(xiàn)遵循依賴(lài)倒置原則的類(lèi)并可以使用BasicCoffeeMachine或PremiumCoffeeMachine類(lèi)來(lái)沖泡咖啡,您需要應(yīng)用“打開(kāi)/關(guān)閉”和“ Liskov替換”原理。這需要少量的重構(gòu),在此期間您將為兩個(gè)類(lèi)引入接口抽象。
引入抽象
這兩種咖啡機(jī)的主要任務(wù)是沖泡咖啡。但是它們使您可以沖泡不同種類(lèi)的咖啡。如果使用BasicCoffeeMachine,則只能沖泡過(guò)濾咖啡,而使用PremiumCoffeeMachine,則可以沖泡過(guò)濾咖啡或濃縮咖啡。那么,哪種接口抽象最適合兩個(gè)類(lèi)?
正如所有咖啡愛(ài)好者都會(huì)同意的那樣,過(guò)濾咖啡和濃縮咖啡之間存在巨大差異。這就是為什么我們使用不同的機(jī)器來(lái)釀造它們的原因,即使如此,某些機(jī)器也可以做到。因此,我建議創(chuàng)建兩個(gè)獨(dú)立的抽象:
所述FilterCoffeeMachine接口定義了咖啡brewFilterCoffee()方法,并得到由能夠沖泡過(guò)濾咖啡的所有咖啡機(jī)類(lèi)實(shí)現(xiàn)的。
您可以用來(lái)釀造意式濃縮咖啡的所有類(lèi)均實(shí)現(xiàn)EspressoMachine接口,該接口定義了Coffee brewEspresso()方法。
如下面的代碼片段所示,兩個(gè)接口的定義都非常簡(jiǎn)單。
public interface CoffeeMachine {
Coffee brewFilterCoffee();}public interface EspressoMachine {
Coffee brewEspresso();}在下一步中,您需要重構(gòu)兩個(gè)咖啡機(jī)類(lèi),以便它們實(shí)現(xiàn)這兩個(gè)接口中的一個(gè)或兩個(gè)。
重構(gòu)BasicCoffeeMachine類(lèi)
讓我們從BasicCoffeeMachine類(lèi)開(kāi)始。您可以使用它來(lái)沖泡過(guò)濾咖啡,因此它應(yīng)該實(shí)現(xiàn)CoffeeMachine接口。該類(lèi)已經(jīng)實(shí)現(xiàn)了brewFilterCoffee()方法。您只需要將實(shí)現(xiàn)CoffeeMachine添加到類(lèi)定義中。
public class BasicCoffeeMachine implements CoffeeMachine {
private Configuration config;
private Map<CoffeeSelection, GroundCoffee> groundCoffee;
private BrewingUnit brewingUnit;
public BasicCoffeeMachine(Map<CoffeeSelection, GroundCoffee> coffee) {
this.groundCoffee = coffee;
this.brewingUnit = new BrewingUnit();
this.config = new Configuration(30, 480);
}
@Override
public Coffee brewFilterCoffee() {
// get the coffee
GroundCoffee groundCoffee = this.groundCoffee.get(CoffeeSelection.FILTER_COFFEE);
// brew a filter coffee
return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE, groundCoffee, this.config.getQuantityWater());
}
public void addGroundCoffee(CoffeeSelection sel, GroundCoffee newCoffee) throws CoffeeException {
GroundCoffee existingCoffee = this.groundCoffee.get(sel);
if (existingCoffee != null) {
if (existingCoffee.getName().equals(newCoffee.getName())) {
existingCoffee.setQuantity(existingCoffee.getQuantity() + newCoffee.getQuantity());
} else {
throw new CoffeeException("Only one kind of coffee supported for each CoffeeSelection.");
}
} else {
this.groundCoffee.put(sel, newCoffee);
}
} }重構(gòu)PremiumCoffeeMachine類(lèi)
PremiumCoffeeMachine的重構(gòu)也不需要很多工作。您可以使用咖啡機(jī)沖煮過(guò)濾咖啡和濃縮咖啡,因此PremiumCoffeeMachine類(lèi)應(yīng)實(shí)現(xiàn)CoffeeMachine和EspressoMachine接口。該類(lèi)已經(jīng)實(shí)現(xiàn)了兩個(gè)接口定義的方法。您只需要聲明它實(shí)現(xiàn)了接口。
import java.util.HashMap;import java.util.Map;public class PremiumCoffeeMachine implements CoffeeMachine, EspressoMachine {
private Map<CoffeeSelection, Configuration> configMap;
private Map<CoffeeSelection, CoffeeBean> beans;
private Grinder grinder;
private BrewingUnit brewingUnit;
public PremiumCoffeeMachine(Map<CoffeeSelection, CoffeeBean> beans) {
this.beans = beans;
this.grinder = new Grinder();
this.brewingUnit = new BrewingUnit();
this.configMap = new HashMap<>();
this.configMap.put(CoffeeSelection.FILTER_COFFEE, new Configuration(30, 480));
this.configMap.put(CoffeeSelection.ESPRESSO, new Configuration(8, 28));
}
@Override
public Coffee brewEspresso() {
Configuration config = configMap.get(CoffeeSelection.ESPRESSO);
// grind the coffee beans
GroundCoffee groundCoffee = this.grinder.grind(
this.beans.get(CoffeeSelection.ESPRESSO),
config.getQuantityCoffee());
// brew an espresso
return this.brewingUnit.brew(CoffeeSelection.ESPRESSO, groundCoffee,
config.getQuantityWater());
}
@Override
public Coffee brewFilterCoffee() {
Configuration config = configMap.get(CoffeeSelection.FILTER_COFFEE);
// grind the coffee beans
GroundCoffee groundCoffee = this.grinder.grind(
this.beans.get(CoffeeSelection.FILTER_COFFEE),
config.getQuantityCoffee());
// brew a filter coffee
return this.brewingUnit.brew(CoffeeSelection.FILTER_COFFEE,
groundCoffee,config.getQuantityWater());
}
public void addCoffeeBeans(CoffeeSelection sel, CoffeeBean newBeans) throws CoffeeException {
CoffeeBean existingBeans = this.beans.get(sel);
if (existingBeans != null) {
if (existingBeans.getName().equals(newBeans.getName())) {
existingBeans.setQuantity(existingBeans.getQuantity() + newBeans.getQuantity());
} else {
throw new CoffeeException("Only one kind of coffee supported for each CoffeeSelection.");
}
} else {
this.beans.put(sel, newBeans);
}
}}直接依賴(lài)于其中一個(gè)實(shí)現(xiàn)類(lèi)的唯一代碼是CoffeeAppStarter類(lèi),該類(lèi)實(shí)例化CoffeeApp對(duì)象并提供CoffeeMachine接口的實(shí)現(xiàn)。您可以通過(guò)使用依賴(lài)項(xiàng)注入框架(例如Spring或CDI)在運(yùn)行時(shí)解析依賴(lài)項(xiàng)來(lái)完全避免這種編譯時(shí)依賴(lài)項(xiàng)。

public class CoffeeApp {
private CoffeeMachine coffeeMachine;
public CoffeeApp(CoffeeMachine coffeeMachine) {
this.coffeeMachine = coffeeMachine }
public Coffee prepareCoffee() throws CoffeeException {
Coffee coffee = this.coffeeMachine.brewFilterCoffee();
System.out.println("Coffee is ready!");
return coffee;
} }import java.util.HashMap;import java.util.Map;public class CoffeeAppStarter {
public static void main(String[] args) {
// create a Map of available coffee beans
Map<CoffeeSelection, CoffeeBean> beans = new HashMap<CoffeeSelection, CoffeeBean>();
beans.put(CoffeeSelection.ESPRESSO, new CoffeeBean(
"My favorite espresso bean", 1000));
beans.put(CoffeeSelection.FILTER_COFFEE, new CoffeeBean(
"My favorite filter coffee bean", 1000))
// get a new CoffeeMachine object
PremiumCoffeeMachine machine = new PremiumCoffeeMachine(beans);
// Instantiate CoffeeApp
CoffeeApp app = new CoffeeApp(machine);
// brew a fresh coffee
try {
app.prepareCoffee();
} catch (CoffeeException e) {
e.printStackTrace();
}
}}
概要
依賴(lài)倒置原則是我們?cè)诒鞠盗兄杏懻摰牡谖鍌€(gè)也是最后一個(gè)設(shè)計(jì)原則。它引入了上層和下層軟件組件之間的接口抽象,以消除它們之間的依賴(lài)關(guān)系。
正如在示例項(xiàng)目中看到的那樣,您僅需要在代碼庫(kù)中應(yīng)用“打開(kāi)/關(guān)閉”和“ Liskov替換”原理。完成此操作后,您的類(lèi)也將遵循“依賴(lài)倒置原則”。這使您能夠更改上層和下層組件,而不會(huì)影響任何其他類(lèi),只要您不更改任何接口抽象即可。
