依賴倒置(DIP)
-
控制反轉(zhuǎn)實際上,控制反轉(zhuǎn)是一個比較籠統(tǒng)的設(shè)計思想驮审,并不是一種具體的實現(xiàn)方法鲫寄,一般用來指導(dǎo)框架層面的設(shè)計。這里所說的“控制”指的是對程序執(zhí)行流程的控制疯淫,而“反轉(zhuǎn)”指的是在沒有使用框架之前地来,程序員自己控制整個程序的執(zhí)行。在使用框架之后峡竣,整個程序的執(zhí)行流程通過框架來控制靠抑。流程的控制權(quán)從程序員“反轉(zhuǎn)”給了框架量九。
public class UserServiceTest { public static boolean doTest(){ return false; } //代碼流程都由程序員來控制 public static void main(String[] args) {//注意:這部分邏輯可以放到框架中 if(doTest()){ System.err.println("Test successed!"); }else { System.err.println("Test failed!"); } } } /** * JunitApplication類 * * 框架提供了一個可擴(kuò)展的代碼骨架适掰,用來組裝對象、管理整個執(zhí)行流程荠列。程序員利用框架進(jìn)行開發(fā)的時候类浪, * 只需要往預(yù)留的擴(kuò)展點上,添加跟自己業(yè)務(wù)相關(guān)的代碼肌似,就可以利用框架來驅(qū)動整個程序流程的執(zhí)行费就。 * * 這里的“控制”指的是對程序執(zhí)行流程的控制,而“反轉(zhuǎn)”指的是在沒有使用框架之前川队,程序員自己控制整個 * 程序的執(zhí)行力细。在使用框架之后,整個程序的執(zhí)行流程可以通過框架來控制固额。流程的控制權(quán)從程序員“反轉(zhuǎn)”到了框架眠蚂。 * */ public class JunitApplication { private static final List<TestCase> testCases = new ArrayList<>(); public static void register(TestCase testCase){ testCases.add(testCase); } //程序啟動入口 public static final void main(String[] args) { for (TestCase testCase : testCases) { testCase.run(); } } } public abstract class TestCase { public void run() { if (doTest()) { System.out.println("Test succeed."); }else{ System.out.println("Test failed."); } } //框架預(yù)留的擴(kuò)展點 public abstract boolean doTest(); } public class IocUserServiceTest extends TestCase{ @Override public boolean doTest() { return false; } public static void main(String[] args) { // 注冊操作還可以通過配置的方式來實現(xiàn),不需要程序員顯示調(diào)用register() JunitApplication.register(new IocUserServiceTest()); } }
-
依賴注入依賴注入和控制反轉(zhuǎn)恰恰相反斗躏,它是一種具體的編碼技巧逝慧。我們不通過 new 的方式在類內(nèi)部創(chuàng)建依賴類的對象,而是將依賴的類對象在外部創(chuàng)建好之后,通過構(gòu)造函數(shù)笛臣、函數(shù)參數(shù)等方式傳遞(或注入)給類來使用云稚。
public class Notification { private MessageSender messageSender; // 非依賴注入實現(xiàn)方式 public Notification() { this.messageSender = new MessageSender(); } public void sendMessage(String cellphone, String message){ this.messageSender.send(cellphone,message); } } public class Notification { private MessageSender messageSender; //依賴注入實現(xiàn)方式,注入對象可以是抽象類也可以是接口 //通過依賴注入的方式來將依賴的類對象傳遞進(jìn)來,這樣就提高了代碼的擴(kuò)展性沈堡,我們可以靈活地替換依賴的類,此處使用 // 接口+組合方式替代繼承可以顯著提高代碼的擴(kuò)展性 public Notification(MessageSender messageSender) { this.messageSender = messageSender; } public void sendMessage(String cellphone, String message) { this.messageSender.send(cellphone, message); } } public class Demo { public static void main(String[] args) { //Notification notification = new Notification(); //notification.sendMessage("cellphone","非依賴注入實現(xiàn)方式"); MessageSender messageSender = new MessageSender(); Notification notification = new Notification(messageSender); notification.sendMessage("cellphone","依賴注入實現(xiàn)方式"); } }
依賴注入框架我們通過依賴注入框架提供的擴(kuò)展點静陈,簡單配置一下所有需要的類及其類與類之間依賴關(guān)系,就可以實現(xiàn)由框架來自動創(chuàng)建對象踱蛀、管理對象的生命周期窿给、依賴注入等原本需要程序員來做的事情。
-
依賴反轉(zhuǎn)原則也叫作依賴倒置原則率拒,英文翻譯是 Dependency Inversion Principle崩泡,縮寫為 DIP。它的英文描述:High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.翻譯成中文猬膨,大概意思就是:高層模塊(high-level modules)不要依賴低層模塊(low-level)角撞。高層模塊和低層模塊應(yīng)該通過抽象(abstractions)來互相依賴。除此之外勃痴,抽象(abstractions)不要依賴具體實現(xiàn)細(xì)節(jié)(details)谒所,具體實現(xiàn)細(xì)節(jié)(details)依賴抽象(abstractions)。
所謂高層模塊和低層模塊的劃分沛申,簡單來說就是劣领,在調(diào)用鏈上,調(diào)用者屬于高層铁材,被調(diào)用者屬于低層尖淘。這條原則跟控制反轉(zhuǎn)有點類似,主要用來指導(dǎo)框架層面的設(shè)計著觉。高層模塊不依賴低層模塊村生,它們共同依賴同一個抽象。抽象不要依賴具體實現(xiàn)細(xì)節(jié)饼丘,具體實現(xiàn)細(xì)節(jié)依賴抽象趁桃。
我們拿 Tomcat 這個 Servlet 容器作為例子來解釋一下。Tomcat 是運行 Java Web 應(yīng)用程序的容器肄鸽。我們編寫的 Web 應(yīng)用程序代碼只需要部署在 Tomcat 容器下卫病,便可以被 Tomcat 容器調(diào)用執(zhí)行。按照之前的劃分原則典徘,Tomcat 就是高層模塊蟀苛,我們編寫的 Web 應(yīng)用程序代碼就是低層模塊。Tomcat 和應(yīng)用程序代碼之間并沒有直接的依賴關(guān)系烂斋,兩者都依賴同一個“抽象”屹逛,也就是 Servlet 規(guī)范础废。Servlet 規(guī)范不依賴具體的 Tomcat 容器和應(yīng)用程序的實現(xiàn)細(xì)節(jié),而 Tomcat 容器和應(yīng)用程序依賴 Servlet 規(guī)范罕模。
KISS原則
KISS 原則是保持代碼可讀和可維護(hù)的重要手段评腺。KISS 原則中的“簡單”并不是以代碼行數(shù)來考量的。代碼行數(shù)越少并不代表代碼越簡單淑掌,我們還要考慮邏輯復(fù)雜度蒿讥、實現(xiàn)難度、代碼的可讀性等抛腕。而且芋绸,本身就復(fù)雜的問題,用復(fù)雜的方法解決担敌,并不違背 KISS 原則摔敛。除此之外,同樣的代碼全封,在某個業(yè)務(wù)場景下滿足 KISS 原則马昙,換一個應(yīng)用場景可能就不滿足了。
對于如何寫出滿足 KISS 原則的代碼刹悴,我還總結(jié)了下面幾條指導(dǎo)原則:
不要使用同事可能不懂的技術(shù)來實現(xiàn)代碼行楞;
不要重復(fù)造輪子,
要善于使用已經(jīng)有的工具類庫土匀;不要過度優(yōu)化子房。
/**
* IpValidater類
*
*/
public class IpValidater {
// 第一種實現(xiàn)方式: 使用正則表達(dá)式;不滿足KISS原則
//1. 正則表達(dá)式本身是比較復(fù)雜的就轧,寫出完全沒有 bug 的正則表達(dá)本身就比較有挑戰(zhàn)证杭;
// 2.并不是每個程序員都精通正則表達(dá)式。對于不怎么懂正則表達(dá)式的同事來說钓丰,看懂并且維護(hù)這段正則表達(dá)式是比較困難的躯砰。
// 這種實現(xiàn)方式會導(dǎo)致代碼的可讀性和可維護(hù)性變差
public boolean isValidIpAddressV1(String ipAddress) {
if (StringUtils.isBlank(ipAddress)) return false;
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
return ipAddress.matches(regex);
}
// 第二種實現(xiàn)方式: 使用現(xiàn)成的工具類每币;滿足KISS原則
public boolean isValidIpAddressV2(String ipAddress) {
if (StringUtils.isBlank(ipAddress)) return false;
String[] ipUnits = StringUtils.split(ipAddress, '.');
if (ipUnits.length != 4) {
return false;
}
for (int i = 0; i < 4; ++i) {
int ipUnitIntValue;
try {
ipUnitIntValue = Integer.parseInt(ipUnits[i]);
} catch (NumberFormatException e) {
return false;
}
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
return false;
}
if (i == 0 && ipUnitIntValue == 0) {
return false;
}
}
return true;
}
// 第三種實現(xiàn)方式: 不使用任何工具類携丁;不滿足KISS原則
// 第三種要比第二種更加有難度,更容易寫出 bug兰怠。從可讀性上來說梦鉴,第二種實現(xiàn)方式的代碼邏輯更清晰、更好理解揭保。
public boolean isValidIpAddressV3(String ipAddress) {
char[] ipChars = ipAddress.toCharArray();
int length = ipChars.length;
int ipUnitIntValue = -1;
boolean isFirstUnit = true;
int unitsCount = 0;
for (int i = 0; i < length; ++i) {
char c = ipChars[i];
if (c == '.') {
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
if (isFirstUnit && ipUnitIntValue == 0) return false;
if (isFirstUnit) isFirstUnit = false;
ipUnitIntValue = -1;
unitsCount++;
continue;
}
if (c < '0' || c > '9') {
return false;
}
if (ipUnitIntValue == -1) ipUnitIntValue = 0;
ipUnitIntValue = ipUnitIntValue * 10 + (c - '0');
}
if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
if (unitsCount != 3) return false;
return true;
}
}
/**
* ContentValidater類
*
* KMP 算法以快速高效著稱肥橙。當(dāng)我們需要處理長文本字符串匹配問題(幾百 MB 大小文本內(nèi)容的匹配),
* 或者字符串匹配是某個產(chǎn)品的核心功能(比如 Vim秸侣、Word 等文本編輯器)存筏,又或者字符串匹配算法是
* 系統(tǒng)性能瓶頸的時候宠互,我們就應(yīng)該選擇盡可能高效的 KMP 算法。而 KMP 算法本身具有邏輯復(fù)雜椭坚、實現(xiàn)
* 難度大予跌、可讀性差的特點。本身就復(fù)雜的問題善茎,用復(fù)雜的方法解決券册,并不違背 KISS 原則。
*
* 不過垂涯,平時的項目開發(fā)中涉及的字符串匹配問題烁焙,大部分都是針對比較小的文本。在這種情況下耕赘,直接調(diào)
* 用編程語言提供的現(xiàn)成的字符串匹配函數(shù)就足夠了骄蝇。如果非得用 KMP 算法、BM 算法來實現(xiàn)字符串匹配操骡,
* 那就真的違背 KISS 原則了乞榨。
*
* 也就是說,同樣的代碼当娱,在某個業(yè)務(wù)場景下滿足 KISS 原則吃既,換一個應(yīng)用場景可能就不滿足了。
*/
public class ContentValidater {
// KMP algorithm: a, b分別是主串和模式串跨细;n, m分別是主串和模式串的長度鹦倚。
public static int kmp(char[] a, int n, char[] b, int m) {
int[] next = getNexts(b, m);
int j = 0;
for (int i = 0; i < n; ++i) {
while (j > 0 && a[i] != b[j]) { // 一直找到a[i]和b[j]
j = next[j - 1] + 1;
}
if (a[i] == b[j]) {
++j;
}
if (j == m) { // 找到匹配模式串的了
return i - m + 1;
}
}
return -1;
}
// b表示模式串,m表示模式串的長度
private static int[] getNexts(char[] b, int m) {
int[] next = new int[m];
next[0] = -1;
int k = -1;
for (int i = 1; i < m; ++i) {
while (k != -1 && b[k + 1] != b[i]) {
k = next[k];
}
if (b[k + 1] == b[i]) {
++k;
}
next[i] = k;
}
return next;
}
}
YAGNI原則
YAGNI 原則的英文全稱是:You Ain’t Gonna Need It冀惭。直譯就是:你不會需要它震叙。當(dāng)用在軟件開發(fā)中的時候,它的意思是:不要去設(shè)計當(dāng)前用不到的功能散休;不要去編寫當(dāng)前用不到的代碼媒楼。實際上,這條原則的核心思想就是:不要做過度設(shè)計戚丸。
當(dāng)然划址,這并不是說我們就不需要考慮代碼的擴(kuò)展性。我們還是要預(yù)留好擴(kuò)展點限府,等到需要的時候夺颤,再去實現(xiàn);我們不要在項目中提前引入不需要依賴的開發(fā)包胁勺。
總結(jié):YAGNI 原則跟 KISS 原則并非一回事兒世澜。KISS 原則講的是“如何做”的問題(盡量保持簡單),而 YAGNI 原則說的是“要不要做”的問題(當(dāng)前不需要的就不要做)署穗。