4.接口隔離原則
定義:
接口隔離原則是針對不同的模塊提供專門的接口,而不是建立一個龐大的臃腫的接口供客戶端訪問。意在接口設計的粒度越小,越靈活,方法越少,越容易擴展,而且儘量只有一個方法。接口隔離原則與單一職責類似,但是出發角度不同。單一職責是講得的是職責單一,只有一種產生影響的原因。而接口隔離講得是接口級別,比單一職責範圍限定更窄。
規範規約:
- 接口要儘量小
儘量滿足單一職責原則
- 接口要高內聚
提高模塊內的聯繫,減少對外交互,儘量少public,如果不可以避免,那麼是否可以單獨抽取一個對外交換的接口,和其他接口區分。
- 定製服務
可以爲特定的場景提供定製接口,保證模塊間的耦合性的同時,不影像其他業務。
- 接口設計是有限度的
不是說接口設計粒度越小就更優秀,接口粒度越小,系統靈活性提高,但是隨之代理的問題就是,增加了管理的難度。所以接口設計還是要有一限度,這個需要更具經驗而來。
舉例:
聲明一個喫飯工具的接口EatingTool ,接口含有兩個抽象方法:cuttFood和pickFood。聲明瞭一個筷子類Chopstick,筷子了實現了接口EatingTool ,並實現了cuttFood和pickFood方法。測試類用筷子喫攤雞蛋的時候,先用筷子把攤雞蛋切成小塊,然後再夾起來送到嘴裏,這個設計和過程都是合乎情理的。
/**
* 喫飯工具接口
*/
public interface EatingTool {
//切割食物
void cuttFood(String food);
//拾取食物
void pickFood(String food);
}
/**
* 筷子類
*/
public class Chopstick implements EatingTool{
@Override
public void cuttFood(String food) {
System.out.println("切割了 :" + food);
}
@Override
public void pickFood(String food) {
System.out.println("夾起了 :" + food);
}
}
public class TestEat {
public static void main(String[] args) {
Chopstick chopstick = new Chopstick();
chopstick.cuttFood("攤雞蛋");
chopstick.pickFood("攤雞蛋");
}
}
打印結果:
切割了 :攤雞蛋
夾起了 :攤雞蛋
但是有一天,筷子不見了,只剩下刀叉,但是攤雞蛋還是要喫的,用刀叉也一樣,但事實錯了。聲明一個刀類,也實現了工具接口EatingTool,但是這裏就有問題了。餐刀只能用來切割食物,不能拾取食物啊,用餐刀喫飯,氣氛有點怪怪的。EatingTool有兩個功能,不滿足單一職責,怎麼辦呢? 拆分?EatingTool保留沒有具體細節,將切割食物和拾取食物拆分出兩個接口。於是又喫到了攤雞蛋,代碼如下:
/**
* 喫飯工具接口
*/
public interface EatingTool {
}
/**
* 切割食物的工具
*/
public interface CuttFoodTool extends EatingTool{
//切割食物
void cuttFood(String food);
}
/**
* 拾取食物
*/
public interface PickFoodTool extends EatingTool{
//拾取食物
void pickFood(String food);
}
/**
* 餐刀
*/
public class Knife implements CuttFoodTool{
@Override
public void cuttFood(String food) {
System.out.println("切割 "+food );
}
}
/**
* 餐叉
*/
public class Fork implements PickFoodTool{
@Override
public void pickFood(String food) {
System.out.println("拾取 "+ food);
}
}
public class TestEat {
public static void main(String[] args) {
Knife knife = new Knife();
knife.cuttFood("攤雞蛋");
Fork fork = new Fork();
fork.pickFood("攤雞蛋");
}
}
執行結果:
切割 攤雞蛋
拾取 攤雞蛋
不過此時的筷子類也需要適當修改,分別實現CuttFoodTool,PickFoodTool兩個接口
/**
* 筷子類
*/
public class Chopstick implements CuttFoodTool,PickFoodTool{
@Override
public void cuttFood(String food) {
System.out.println("切割了 :" + food);
}
@Override
public void pickFood(String food) {
System.out.println("夾起了 :" + food);
}
}
優勢:
- 接口的容量越小,越靈活,越容易擴展
- 提高了內聚,一個接口只有一個工作
- 降低模塊間的耦合性
實踐:
單一接口職責,不單指名義上的接口,其實也包含了實體類,一個接口只服務與一個模塊或業務邏輯。接口的設計要儘量避免臃腫和龐大,接口越臃腫龐大受影響的因素越多,影響的範圍越大。當發現接口已經被污染時,能修改拆分儘量修改差分,不行的化就採用適配器的方式進行適配。
5.迪米特法則Least Knowledge Principle(LKP)
定義:
迪米特法則規定的是一個對象應該讓另一個對象有更少的瞭解。在軟件工程中的體現是一個類應該提供更少的public方法,相對於調用方,就是隻是知道public 方法,其他細節不想了解,也無需瞭解。
迪米特法則意在兩個類之間松耦合,耦合體現的方式有依賴,組合, 聚和。迪米特法則,將這種關係描述朋友,而朋友類是指方法的輸入輸出參數,聲明的變量,內部類不算是朋友類。朋友類是產生直接耦合關係的類。
舉例:
有些話只對朋友說
比如A類和C類分屬在不同的模塊,現在A和C需要關聯,但是又不想A和C過度耦合,那麼就可以增加一個B類,來中間調和一下。也就是朋友類,其實朋友類也就是過渡類。
朋友之間也是有距離的。
過渡類可以原本關聯的類松耦合但是對於對於這個朋友也要有限定要求,朋友之間也不是無話不談,封裝的方法也要有內聚性。否則結果會增加系統的複雜性。
班主任讓班長上課前點名統計一下人數,老師不必親自去數班裏來了多少人,只是想知道最後的結果,通過班長去統計人數。申明一個老師類Teacher ,告知班長點名的方法inform()。申明一個班長類Monitor,班長有一個公開的點名的方法roolCall,但是具體怎麼點名是私有的count()。老師是知道班長要點名,通過monitor參數調用roolCall方法。但是具體點名的方法,班長是沒和老師說,老師也不用知道。可能是按人頭數的,也可能是班長來的時候已經快上課了,爲了節省時間,看了一眼簽到表就告訴班主任了。代碼如下:
/**
* 班主任
*/
public class Teacher {
//告知班長點名
public void inform(Monitor monitor){
monitor.roolCall();
}
}
/import java.util.List;
/**
* 班長
*/
public class Monitor {
//全班學生
private List<String> sudentList;
public Monitor(List<String> sudentList){
this.sudentList = sudentList;
}
/**
* 統計人數
*/
public void roolCall(){
int count = count();
System.out.println("全班一共來了 " + count + "人");
}
private int count(){
return sudentList.size();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 迪米特法則測試類
*/
public class TestTM {
public static void main(String[] args) {
//學生陸續進行教室
List<String> list = new ArrayList<>();
list.add("小黃");
list.add("小紅");
list.add("小藍");
list.add("班長");
/**
* 老師讓班長點名統計來了多少同學
*/
//老師類
Teacher teacher = new Teacher();
//班長類
Monitor monitor = new Monitor(list);
teacher.inform(monitor);
}
}
打印結果:
全班一共來了 4人
優點:
迪米特法則將通過過度類使兩個之間耦合度降低。
實踐:
迪米特法制雖然實現瞭解耦,但是這種解耦也是有限度的解耦。可以通過兩個本來沒有關係的類通過過度類產生依賴,降低了耦合的強度,但是如果使用不慎,有可能產生大量的過度類,這樣就得不償失了。
6、開閉原則
定義:
開閉原則的定義是一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。開閉原則是設計的基礎,需求是會發生變化的,很多情況是在前一個需求已經穩定投產後,後續需求又需求的原有基礎上有改動。而開閉原則這是限定了如何來應對這種變化,畢竟誰也無法保證設計的接口或者類始終會滿足這種變化。開閉原則對於這種變化原則上是進行擴展,而不是在原有基礎上進行修改。不過變化也是多種多樣的,規則的粒度也可調整比如可以將變化分類選擇不同的策略:
- 邏輯變化:
比如只是涉及一條邏輯修改,並且其他類也是遵守對應規則,那這種情況是允許修改的。反之,如果會造成的影響,這種情況應該是要新增一個方法,來修改這個邏輯
- 模塊變化
一個模塊變化,會對其他的模塊產生影響,特別是一個低層次的模塊變化必然引起高層模塊的變化,因此在通過擴展完成變化時,高層次的模塊修改是必然的,剛剛的書籍打折處理就是類似的處理模塊,該部分的變化甚至會引起界面的變化。
- 可見試圖的變化
比如界面需要增加一個顯示字段,如果原因返回內容已經存在這個字段,只是頁面增加顯示還是簡單的。但是如果這個字段還要通過連表查詢後計算得出,那這種變化就比較恐怖了,擴展就是很有必要的。還有就是在查詢時,起初是查詢一條,後來必須支持多條查詢,這就需要在設計初期是否要考慮這種場景了。
優點:
開閉原則像是欲按,真對是未來可能產生的影響而預先考慮的方案,將風險和影響降低。
實踐:
- 1.抽象約束
- 2.元數據(metadata)控制模塊行爲
- 3.制定項目章程
- 4.封裝變化
六大設計原則總結:
學習了完了六大設計原則,感知最深的應該是高內聚,松耦合對於一個系統來說是多麼的重要。設計原則都是圍繞如何做到這一點。不過原則不是硬性標準,不懂得變通,生搬硬套,那帶來的更多的困擾,所以還是需要結合實際來準確運用。
單一職責原則:是對接口、類,行爲的限定
里氏替換原則:將父類作爲傳輸條件,提高代碼的健壯性
依賴倒置原則:模塊之間的關聯應該通過接口或者抽象類
接口隔離原則:從接口層(接口,類)來看,接口越單一越意於擴張和維護
迪米特法則原因:兩個類通過過渡類來建立耦合關係,過渡類具有高內聚的特點
開閉原則:影響小直接修改,影響大直接擴展。越底層,越要避免修改,進行擴張。影響是從低到高會逐層放大。
參考:《設計模式禪》 秦小波著