前言
個人風格系列筆記,不會太詳細,不喜勿噴,適用於複習設計模式而做。
概要
- 一個好的程式猿/媛敲出來的程式碼應該是可維護、可複用、可擴充套件的,也就是具有較好的靈活性。
- 為了達到以上目的,在還沒敲程式碼之前,需要事先考慮通過何種方式能夠使自己的程式的耦合度降低,最基本的便是物件導向的封裝、繼承、多型。
- 但這往往是不夠的,需要根據實際情況選擇合適的設計模式使得程式變得更加靈活,容易修改,並且易於複用。
- 複用並不是複製的意思。在實際開發當中,有時候會遇到一些類似的功能,有一部分已經是在別的 Service 中實現了(整個方法的功能是不一樣的),但可能出於專案趕進度的時候,也可能做這個功能的員工水平不足,於是乎直接將此處程式碼一頓操作 ctrl + C / V 一切搞定。但對日後維護的人來說是一場災難,萬一又是自己,日後少不了“早知如此,何必當初”的感嘆了。養成好的編碼習慣很重要。
- 話說得好聽誰都會,正是因為人都有成長的過程,所以對業務的理解轉化為程式,以及編寫程式碼的優美程度也是一步一個腳印走出來的。是否能夠做到真正的鬆耦合,又或者“鬆”的過度導致程式過於臃腫,都跟個人的層次有關。付出不一定有收穫,但不付出,肯定沒有收穫。
拋磚引玉
案例
實現一個簡單計算器程式碼(加減乘除)
普通實現方式
/**
* 運算類
* Created by callmeDevil on 2019/5/26.
*/
public class Operation {
/**
* 獲取運算結果
* @param numA 數值A
* @param numB 數值B
* @param operate 運算子
* @return
*/
public static double getResult(double numA, double numB, String operate) {
double result = 0L;
switch (operate) {
case "+":
result = numA + numB;
break;
case "-":
result = numA - numB;
break;
case "*":
result = numA * numB;
break;
case "/":
result = numA / numB;
break;
default:
break;
}
return result;
}
}
/**
* 測試類
* Created by callmeDevil on 2019/5/26.
*/
public class Test {
public static void main(String[] args) {
Operation operation = new Operation();
double numA = 10;
double numB = 2;
System.out.println("加法:" + operation.getResult(numA, numB, "+"));
System.out.println("減法:" + operation.getResult(numA, numB, "-"));
System.out.println("乘法:" + operation.getResult(numA, numB, "*"));
System.out.println("除法:" + operation.getResult(numA, numB, "/"));
}
}
但是這樣就有個問題,如果這個計算器還需要實現開根號運算呢?難道就直接在Operation類新增一個switch分支就行了嗎?不是的,這樣會讓原來良好執行的程式碼產生變化,風險太大,因此需要將各種運算的邏輯分離開來,此處引入簡單工廠模式。
簡單工廠模式實現
定義
又稱為靜態工廠模式,根據工廠類傳入的引數動態決定建立某種類(這些類都繼承自同一父類或實現同一介面)的例項。
UML圖
結合程式碼有註釋
/**
* 運算基礎類
* Created by callmeDevil on 2019/5/26.
*/
public class BaseOperation {
private double numA;
private double numB;
// 此處省略get、set方法
/**
* 獲取結果
* @return
*/
public double getResult() {
double result = 0L;
return result;
}
}
/**
* 加法運算
* Created by callmeDevil on 2019/5/26.
*/
public class OperationAdd extends BaseOperation {
@Override
public double getResult() {
return getNumA() + getNumB();
}
}
/**
* 減法運算
* Created by callmeDevil on 2019/5/26.
*/
public class OperationSub extends BaseOperation {
@Override
public double getResult() {
return getNumA() - getNumB();
}
}
/**
* 乘法運算
* Created by callmeDevil on 2019/5/26.
*/
public class OperationMul extends BaseOperation {
@Override
public double getResult() {
return getNumA() * getNumB();
}
}
/**
* 除法運算
* Created by callmeDevil on 2019/5/26.
*/
public class OperationDiv extends BaseOperation {
@Override
public double getResult() {
if (getNumB() == 0){
throw new RuntimeException("除數不能為0!");
}
return getNumA() / getNumB();
}
}
/**
* 測試類
* Created by callmeDevil on 2019/5/26.
*/
public class Test {
public static void main(String[] args) {
// 實現其他運算只需要更改輸入的運算子即可
BaseOperation operation = OperationFactory.createOperation("+");
operation.setNumA(10);
operation.setNumB(2);
System.out.println("加法:" + operation.getResult());
}
}
相比普通實現方式的好處
將每種運算的具體實現分離在不同的類中,工廠類只需要負責根據輸入的運算子來建立對應的運算類例項即可。比如要一個開方運算是吧,那麼需要建立一個開方運算類,同樣繼承BaseOperation,接著在工廠類中switch中新增一個case分支,然後客戶端便能夠跟其他運算一樣呼叫。
可能有人會問,這裡不也同樣是修改了switch分支條件嗎?有什麼區別?問得好,說明有思考過,但是原因也很明顯,要仔細想想。把四種基礎運算具體實現分離出去,工廠類中就包含了四行建立例項的程式碼,這時修改了switch分支的風險,和普通實現方式的風險相比,哪個大已經不用細說了吧。
總結
這裡案例程式碼比較簡單,所以看起來會非常明朗,想必不必多說讀者應該也能稍有體會。假如每個型別的運算非常複雜,甚至與其他模組關聯,那使用設計模式的好處就體現出來了,比起全部程式碼堆在一個switch case上,在單獨的一個類裡面會更好維護和擴充套件吧?想做到完全不修改switch的程式碼是不可能,最重要的是我們達到了鬆耦合、可複用、可擴充套件目的,同時還降低了風險,有何不可。