1. 程式人生 > >設計原則---介面隔離原則

設計原則---介面隔離原則

本文參考自設計模式之禪(第二版)第四章

1 介面隔離原則的定義

       在講介面隔離原則之前,先明確一下我們的主角—介面。介面分為兩種:

  • 實力介面(Object Interface),在Java中宣告一個類,然後用new關鍵字產生一個例項,它是對一個型別的事物的描述,這是一種介面。比如你定義Person這個類,然後使用Person zhangSan = new Person()產生了一個例項,這個例項要遵從的標準就是Person這個類,Person類就是zhangSan的介面。疑惑?看不懂?不要緊,那是因為讓Java語言浸染的時間太長了,只要知道從這個角度來看,Java中的類也是一種介面。
  • 類介面(Class Interface),Java中經常使用的interface關鍵字定義的介面。

主角已經定義清除了,那什麼是隔離呢?它有兩種定義,如下所示:

  • Clients should not be forced to depend upon interfaces that they don’t use.(客戶端不應該依賴它不需要的介面。)
  • The dependency of one class to another one should depend on the smallest possible interface.(類間的依賴關係應該建立在最小的介面上。)

       新事物的定義一般都比較難理解,晦澀難懂是正常的。我們把這兩個定義剖析一下,先說第一種定義:“客戶端不應該依賴它不需要的介面”,那依賴什麼呢?依賴它需要的介面,客戶端需要什麼介面就提供什麼介面,把不需要的介面剔除掉,那就需要對介面進行細化,保證其純潔性;再看第二種定義:“類間的依賴關係應該建立在最小的介面上”,它要求是最小的介面,也是要求介面細化,介面純潔,與第一個定義如出一轍,只是一個事物的兩種不同描述。        我們可以把這兩個定義概括為一句話:建立單一介面,不要建立臃腫龐大的介面。再通俗一點講:介面儘量細化,同時介面中的方法儘量少。看到這裡大家有可能要疑惑了,這與

設計原則—單一職責原則不是相同的嗎?錯,介面隔離原則與單一職責原則的審視角度是不相同的,單一職責原則要求的是類和介面的職責單一,注重的是職責,這是業務邏輯上的劃分,而介面隔離原則要求介面的方法儘量少。例如一個介面的職責可能包含10個方法,這10個方法都放在一個介面中,並且提供給多個模組訪問,各個模組按照規定的許可權來訪問,在系統外通過文件約束“不使用的方法不要訪問”,按照單一職責原則是允許的,按照介面隔離原則是不允許的,因為它要求“儘量使用多個專門的介面”。專門的介面指什麼?就是指提供給每個模組的都應該是單一介面,提供給幾個模組就應該有幾個介面,而不是建立一個龐大的臃腫的介面,容納所有的客戶端訪問。

2 美女何其多,觀點各不同

       我們舉例來說明介面隔離原則到底對我們提出了什麼要求。現在男生對小姑娘的稱呼,使用頻率最高的應該是“美女”了吧,你在大街上叫一聲“嗨,美女!”估計10個有8個回頭,其中包括那位著名的如花。美女的標準各不相同,首先就需要定義一下什麼是美女:首先要面貌好看,其實是身材要窈窕,然後要有氣質,當然了,這三者各人的排列順序不一樣,總之要成為一名美女就必須具備:面貌、身材和氣質,我們用類圖體現一下星探(當然,你也可以把自己想象成星探)找美女的過程,如下圖1所示: 圖1 星探尋找美女的類圖        定義了一個IPettyGirl介面,宣告所有的美女都應該有goodLooking、niceFigure、greatTemperament,然後又定義了一個抽象類AbstractSearcher,起作用就是搜尋美女並顯示其資訊,只要美女都按照這個規範定義,Searcher(星探)就輕鬆多了,美女類的實現如下程式碼1所示:

public interface IPettyGirl {
	public void goodLooking();
	public void niceFigure();
	public void greatTemperament();
}

       美女的標準定義完畢,具體的美女實現類如下程式碼2所示:

public class PettyGirl implements IPettyGirl {
	private String name;
	//美女都有名字
	public PettyGirl(String _name) {
		this.name = _name;
	}
	public void goodLooking() {
		System.out.println(this.name + "---臉蛋很漂亮");
	}
	public void niceFigure() {
		System.out.println(this.name + "---身材非常棒");
	}
	public void greatTemperament() {
		System.out.println(this.name + "---氣質非常好");
	}
}

       通過三個方法,把對美女的要求都定義了出來,按照這個標準,如果姑娘被排除在美女轉標準之外了。有美女,就有搜尋美女的星探,其具體實現如下程式碼3所示:

public abstract class AbstractSearcher {
	protected IPettyGirl pettyGirl;
	public AbstractSearcher(IPettyGirl _pettyGirl) {
		this.pettyGirl = _pettyGirl;
	}
	public abstract void show();
}

       星探的實現類就比較簡單了,其原始碼如下程式碼4所示:

public class Searcher extends AbstractSearcher {
	public Searcher(IPettyGirl _pettyGirl) {
		super(_pettyGirl);
	}
	//展示美女的資訊
	public void show() {
		System.out.println("-----美女的資訊如下:-----");
		//展示面容
		super.pettyGirl.goodLooking();
		//展示身材
		super.pettyGirl.niceFigure();
		//展示氣質
		super.pettyGirl.greatTemperament();
	}
}

       場景中的兩個角色美女和星探都已經出現了,需要寫一個場景類來串聯起各個角色,場景類的實現如下程式碼5所示:

public class Client {
	//搜尋並展示美女資訊
	public static void main(String[] args) {
		//定義一個美女
		IPettyGirl lingling = new PettyGirl("玲玲");
		AbstractSearcher searcher = new Searcher(lingling);
		searcher.show();
	}
}

       星探搜尋美女的執行結果如下:

-----美女的資訊如下:-----
玲玲---臉蛋很漂亮
玲玲---身材非常棒
玲玲---氣質非常好

       星探尋找美女的程式開發完畢了,執行結果也是正確的。我們回頭來想想這個程式有沒有問題,思考一下IPettyGirl這個介面,這個介面是否做到了最優設計?答案是沒有,還可以對介面進行優化。        我們的審美觀點都在改變,美女的定義也在變化。唐朝的楊貴妃如果活在現在這個年代非得羞愧而死不可,為什麼?胖呀!但是胖並不影響她入選中國四大美女,說明但是的審美觀與現在是有差異的。當然,隨著時代的發展,我們的審美觀也在變化,當你發現一個女孩,臉蛋不怎麼樣,身材以一般般,但是氣質非常好,我相信大部分的人都會把這樣的女孩叫美女,審美素質提升了,就產生了氣質型美女,但是我們的介面卻定義了美女必須是三者都具備,按照這個標準,氣質美女就不算美女,那怎麼辦?可能你要說了,我重新擴充套件一個美女類,只實現greatTemperament方法,其他兩個方法置空,什麼都不寫,不就可以了嗎?聰明,但是行不通!為什麼呢?星探AbstractSearcher依賴的是IPettyGirl介面,它有三個方法,你只實現了一個方法,星探的方法是不是要修改?我們上面的程式打印出來的資訊少了兩條,還讓星探怎麼去辨別是不是美女呢?        分析到這裡,我們發現介面IPettyGril的設計是有缺陷的,過於龐大了,容納了一些可變的因素,根據介面隔離原則,星探AbstractSearcher應該依賴於具有部分特質的女孩子,而我們卻把這些特質都封裝了起來,放到了一個介面中,封裝過度了!問題找到了,我們重新設計一下類圖,修改後的類圖如下圖2所示: 圖2 修改後的星探尋找美女類圖        把原IPettyGirl介面拆分成兩個介面,一種是外形美的美女IGoodBodyGirl,這種美女的特點就是臉蛋和身材極棒,超一流,但是沒有審美素質,比如隨地吐痰,文化程度比較低;另外一種是氣質美的美女IGreatTemperamentGirl,談吐和修養都非常高。我們把一個比較臃腫的介面拆分成了兩個專門的介面,靈活性提高了,可維護性也增加了,不管以後是要外形美的美女還是氣質美的美女都可以輕鬆地通過PettyGirl定義。兩種型別的美女定義如下程式碼6所示:

public interface IGoodBodyGirl {
	//要有姣好的面孔
	public void goodLooking();
	//要有好身材
	public void niceFigure();
}
public interface IGreatTemperament {
	//要有氣質
	public void greatTemperament();
}

       按照臉蛋、身材、氣質都具備才算美女,實現類實現兩個介面,如下程式碼7所示:

public class PettyGirl implements IGoodBodyGirl, IGreatTemperamentGirl {
	private String name;
	//美女都有名字
	public PettyGril(String _name) {
		this.name = _name;
	}
	public void goodLooking() {
		System.out.println(this.name + "---臉蛋很漂亮");
	}
	public void niceFigure() {
		System.out.println(this.name + "---身材非常棒");
	}
	public void greatTemperament() {
		System.out.println(this.name + "---氣質非常好");
	}
}

       通過這樣的重構以後,不管以後是要氣質美女還是要外形美女,都可以保持介面的穩定。當然,你可能要說了,以後可能審美觀點再發生改變,只要臉蛋好看就是美女,那這個IGoodBody介面還是要修改的呀,確實是,但是設計是有限度的,不能無限的考慮未來的變更情況,否則就會陷入設計的泥潭中而不能自拔。        以上把一個臃腫的介面變更為兩個獨立的介面所依賴的原則就是介面隔離原則,讓星探AbstractSearcher依賴兩個專用的介面比依賴一個綜合的介面要靈活。介面是我們設計時對外提供的契約,通過分散定義多個介面,可以預防未來變更的擴散,提高系統的靈活性和可維護性。

3 保證介面的純潔性

       介面隔離原則是對介面進行規範約束,其包含以下4層含義:

  • 介面要儘量小
  • 介面要高內聚
  • 定製服務
  • 介面設計是有限度的
4 最佳實踐

       介面隔離原則是對介面的定義,同時也是對類的定義,介面和類儘量使用原子介面或原子類來組裝。但是,這個原子該怎麼劃分是設計模式中的一大難題,在時間中可以根據以下幾個規則來衡量:

  • 一個介面只服務於一個子模組或業務邏輯
  • 通過業務邏輯亞索介面中的public方法,介面時常去回顧,儘量讓介面達到“滿身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
  • 已經被汙染了的介面,儘量去修改,若變更的風險較大,則採用介面卡模式進行轉化處理;
  • 瞭解環境,拒絕盲從。每個專案或產品都有特定的環境因素,別看到大師是這樣做的你就照抄。千萬別,環境不同,介面拆分的標準就不同。深入瞭解業務邏輯,最好的介面設計就出自你手中!