** 依賴**
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class A {
private B b;
public A() {
this.b = new B();
}
}
public class A {
public void func(B b) { ... }
}
控制反轉(zhuǎn) IOC (Inversion Of Control)
框架提供了一個(gè)可擴(kuò)展的代碼骨架,用來組裝對(duì)象、管理整個(gè)執(zhí)行流程。程序員利用框架進(jìn)行開發(fā)的時(shí)候,只需要往預(yù)留的擴(kuò)展點(diǎn)上,添加跟自己業(yè)務(wù)相關(guān)的代碼,就可以利用框架來驅(qū)動(dòng)整個(gè)程序流程的執(zhí)行。
依賴注入 DI (Dependency Injection)
把有依賴關(guān)系的類放到容器中,解析出這些類的實(shí)例,就是依賴注入。目的是實(shí)現(xiàn)類的解耦。
依賴倒置 DIP (Dependence Inversion Principle)
是指設(shè)計(jì)代碼結(jié)構(gòu)時(shí),高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象。
抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。通過依賴倒置,可以減少類與類之間的耦合性,提高系統(tǒng)的穩(wěn)定性,提高代碼的可讀性和可維護(hù)性,并且能夠降低修改程序所造成的風(fēng)險(xiǎn)。
// 重生之小明 - 進(jìn)擊碼農(nóng)
// MARK: **- 小明和他的手機(jī)**
class Persion {
}
//從前有個(gè)人叫小明小明有三大愛好,逛知乎、玩王者農(nóng)藥和搶微信紅包
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
}
func play(){
//玩農(nóng)藥
}
func grab() {
//搶紅包
}
}
// 但是,小明作為一個(gè)人類,沒有辦法僅靠自己就能實(shí)現(xiàn)以上的功能,他必須依賴一部手機(jī),
class IPhone6 {
func read(name: String) {
print("\(name)打開了知乎然后編了一個(gè)故事")
}
func play(name: String) {
print("\(name)打開了王者農(nóng)藥并送起了人頭")
}
func grab(name: String){
print("\(name)開始搶紅包卻只搶不發(fā)")
}
}
// 小明非常珍惜自己的新手機(jī),每天把它牢牢控制在手心里,所以小明變成了這個(gè)樣子
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
let iphone6 = IPhone6()
iphone6.read(name: self.name)
}
func play(){
//玩農(nóng)藥
let iphone6 = IPhone6()
iphone6.grab(name: self.name)
}
func grab() {
//搶紅包
let iphone6 = IPhone6()
iphone6.grab(name: self.name)
}
}
// 今天是周六,小明不用上班,于是他起床,并依次逛起了知乎,玩王者農(nóng)藥,并搶了個(gè)紅包。
let xiaoming = XiaoMing()
xiaoming.read()
xiaoming.play()
xiaoming.grab()
// 這個(gè)時(shí)候,我們可以在命令行里看到輸出如下
/*
小明打開了知乎然后編了一個(gè)故事
小明打開了王者農(nóng)藥并送起了人頭
小明開始搶紅包卻只搶不發(fā)
*/
//這一天,小明過得很充實(shí),他覺得自己是世界上最幸福的人。
// MARK: - 第二章: 小明的快樂與憂傷
/*
小明和他的手機(jī)曾一起度過了一段美好的時(shí)光,一到空閑時(shí)刻,他就抱著手機(jī),逛知乎,刷微博,玩游戲,他覺得自己根本不需要女朋友,只要有手機(jī)在身邊,就滿足了。可誰能想到,一次次地系統(tǒng)更新徹底打碎了他的夢(mèng)想,他的手機(jī)變得越來越卡頓,電池的使用壽命也越來越短,一直到某一天的寒風(fēng)中,他的手機(jī)終于耐不住寒冷,頭也不回地關(guān)了機(jī)。小明很憂傷,他意識(shí)到,自己要換手機(jī)了。為了能獲得更好的使用體驗(yàn),小明一咬牙,剁手了一臺(tái)iphoneX,這部手機(jī)鈴聲很大,電量很足,還能雙卡雙待,小明很喜歡,但是他遇到一個(gè)問題,就是他之前過度依賴了原來那一部iPhone6,他們之間已經(jīng)深深耦合在一起了,如果要換手機(jī),他就要拿起刀來改造自己,把自己體內(nèi)所有方法中的iphone6 都換成 iphoneX。
漫長的改造過程經(jīng)歷了漫長的改造過程,小明終于把代碼中的 iphone6 全部換成了 iphoneX。雖然很辛苦,但是小明覺得他是快樂的。
*/
class IphoneX {
func read(name: String) {
print("\(name)打開了知乎然后編了一個(gè)故事")
}
func play(name: String) {
print("\(name)打開了王者農(nóng)藥并送起了人頭")
}
func grab(name: String){
print("\(name)開始搶紅包卻只搶不發(fā)")
}
func isBroken() -> Bool {
return true
}
}
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
func read() {
//逛知乎
let iphoneX = IphoneX()
iphone6.read(name: self.name)
}
func play(){
//玩農(nóng)藥
let iphoneX = IphoneX()
iphone6.grab(name: self.name)
}
func grab() {
//搶紅包
let iphoneX = IphoneX()
iphone6.grab(name: self.name)
}
}
/*
于是小明開開心心地帶著手機(jī)去上班了,并在回來的路上被小偷偷走了。為了應(yīng)急,小明只好重新使用那部剛剛被遺棄的iphone6,但是一想到那漫長的改造過程,小明的心里就說不出的委屈,他覺得自己過于依賴手機(jī)了,為什么每次手機(jī)出什么問題他都要去改造他自己,這不僅僅是過度耦合,簡直是本末倒置,他向天空大喊,我不要再控制我的手機(jī)了。
天空中的造物主,也就是作為程序員的我,聽到了他的吶喊,我告訴他,你不用再控制你的手機(jī)了,交給我來管理,把控制權(quán)交給我。這就叫做控制反轉(zhuǎn)。
*/
// MARK: - 第三章:造物主的智慧
/*
小明聽到了我的話,他既高興,又有一點(diǎn)害怕,他跪下來磕了幾個(gè)頭,虔誠地說到:“原來您就是傳說中的造物主,巴格梅克上神。我聽到您剛剛說了 控制反轉(zhuǎn) 四個(gè)字,就是把手機(jī)的控制權(quán)從我的手里交給你,但這只是您的想法,是一種思想罷了,要用什么辦法才能實(shí)現(xiàn)控制反轉(zhuǎn),又可以讓我繼續(xù)使用手機(jī)呢?”
“呵“,身為造物主的我在表現(xiàn)完不屑以后,扔下了八個(gè)大字,“ 依賴注入,依賴倒置!”
接下來,偉大的我開始對(duì)小明進(jìn)行慘無人道的改造,如下
*/
protocol Iphone {
func read(name: String)
func play(name: String)
func grab(name: String)
func isBroken() -> Bool
}
class IPhone6: Iphone {
func isBroken() -> Bool {
return false
}
func read(name: String) {
print("\(name)打開了知乎然后編了一個(gè)故事")
}
func play(name: String) {
print("\(name)打開了王者農(nóng)藥并送起了人頭")
}
func grab(name: String){
print("\(name)開始搶紅包卻只搶不發(fā)")
}
}
class IphoneX: Iphone {
func read(name: String) {
print("\(name)打開了知乎然后編了一個(gè)故事")
}
func play(name: String) {
print("\(name)打開了王者農(nóng)藥并送起了人頭")
}
func grab(name: String){
print("\(name)開始搶紅包卻只搶不發(fā)")
}
func isBroken() -> Bool {
return true
}
}
class XiaoMing: Persion {
private let name: String = "小明"
private let age: Int = 22
private var iphone: Iphone? = nil
init(phone: Iphone) {
self.iphone = phone
}
func read() {
//逛知乎
iphone?.read(name: self.name)
}
func play(){
//玩農(nóng)藥
iphone?.grab(name: self.name)
}
func grab() {
//搶紅包
iphone?.grab(name: self.name)
}
}
// MARK: - 第四章:小明的感悟
//小明的生活開始變得簡單了起來,而他把省出來的時(shí)間都用來寫筆記了,他在筆記本上這樣寫到
/**
我曾經(jīng)有很強(qiáng)的控制欲,過度依賴于我的手機(jī),導(dǎo)致我和手機(jī)之間耦合程度太高,只要手機(jī)出現(xiàn)一點(diǎn)點(diǎn)問題,我都要改造我自己,這實(shí)在是既浪費(fèi)時(shí)間又容易出問題。自從我把控制權(quán)交給了造物主,他每天在喚醒我以前,就已經(jīng)替我選好了手機(jī),我只要按照平時(shí)一樣玩手機(jī)就可以了,根本不用關(guān)心是什么手機(jī)。即便手機(jī)出了問題,也可以由造物主直接搞定,不需要再改造我自己了,我現(xiàn)在買了七部手機(jī),都交給了造物主,每天換一部,美滋滋!我也從其中獲得了這樣的感悟: 如果一個(gè)類A 的功能實(shí)現(xiàn)需要借助于類B,那么就稱類B是類A的依賴,如果在類A的內(nèi)部去實(shí)例化類B,那么兩者之間會(huì)出現(xiàn)較高的耦合,一旦類B出現(xiàn)了問題,類A也需要進(jìn)行改造,如果這樣的情況較多,每個(gè)類之間都有很多依賴,那么就會(huì)出現(xiàn)牽一發(fā)而動(dòng)全身的情況,程序會(huì)極難維護(hù),并且很容易出現(xiàn)問題。要解決這個(gè)問題,就要把A類對(duì)B類的控制權(quán)抽離出來,交給一個(gè)第三方去做,把控制權(quán)反轉(zhuǎn)給第三方,就稱作控制反轉(zhuǎn)(IOC Inversion Of Control)??刂品崔D(zhuǎn)是一種思想,是能夠解決問題的一種可能的結(jié)果,而依賴倒置 (Dependence Inversion Principle)把控制權(quán)抽離出來,而依賴注入(Dependency Injection)就是其最典型的實(shí)現(xiàn)方法 。由第三方(我們稱作IOC容器)來控制依賴,把他通過構(gòu)造函數(shù)、屬性或者工廠模式等方法,注入到類A內(nèi),這樣就極大程度的對(duì)類A和類B進(jìn)行了解耦。
*/
KISS (Keep it Simple and Stupid) 怎么做
讓代碼盡可能簡單,目的是保持代碼可讀和可維護(hù)性
- 代碼行數(shù)越少就越“簡單”嗎?
// 第一種實(shí)現(xiàn)方式: 使用正則表達(dá)式
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);
}
// 第二種實(shí)現(xiàn)方式: 使用現(xiàn)成的工具類
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;
}
// 第三種實(shí)現(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;
}
代碼邏輯復(fù)雜就違背 KISS 原則嗎?
/**
剛剛我們提到,并不是代碼行數(shù)越少就越“簡單”,還要考慮邏輯復(fù)雜度、實(shí)現(xiàn)難度、代碼的可讀性等。那如果一段代碼的邏輯復(fù)雜、實(shí)現(xiàn)難度大、可讀性也不太好,是不是就一定違背 KISS 原則呢?
*/
// 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;
}
/**
KMP 算法以快速高效著稱。當(dāng)我們需要處理長文本字符串匹配問題(幾百 MB 大小文本內(nèi)容的匹配),或者字符串匹配是某個(gè)產(chǎn)品的核心功能(比如 Vim、Word 等文本編輯器),又或者字符串匹配算法是系統(tǒng)性能瓶頸的時(shí)候,我們就應(yīng)該選擇盡可能高效的 KMP 算法。而 KMP 算法本身具有邏輯復(fù)雜、實(shí)現(xiàn)難度大、可讀性差的特點(diǎn)。本身就復(fù)雜的問題,用復(fù)雜的方法解決,并不違背 KISS 原則。
不過,平時(shí)的項(xiàng)目開發(fā)中涉及的字符串匹配問題,大部分都是針對(duì)比較小的文本。在這種情況下,直接調(diào)用編程語言提供的現(xiàn)成的字符串匹配函數(shù)就足夠了。如果非得用 KMP 算法、BM 算法來實(shí)現(xiàn)字符串匹配,那就真的違背 KISS 原則了。也就是說,同樣的代碼,在某個(gè)業(yè)務(wù)場景下滿足 KISS 原則,換一個(gè)應(yīng)用場景可能就不滿足了。
*/
如何寫出滿足 KISS 原則的代碼?
不要使用同事可能不懂的技術(shù)來實(shí)現(xiàn)代碼;
不要重復(fù)造輪子,要善于使用已經(jīng)有的工具類庫;
不要過度優(yōu)化。
YAGNI (You Ain’t Gonna Need It) 要不要做
不要做過度設(shè)計(jì)
- 無用功能的設(shè)計(jì)
- 無用第三方庫的導(dǎo)入