面向接口編程是很多軟件架構設計理論都倡導的編程方式,學習Java自然少不了這一部分,下面是我在學習過程中整理出來的關於如何在Java中實現面向接口編程的知識。分享出來,有不對之處還請大家指正。
接口體現的是一種規范和實現分離的設計哲學,充分利用接口可以極好地降低程序各模塊之間的耦合,從而提高系統的可擴展性和可維護性。基於這種原則,通常推薦“面向接口”編程,而不是面向實現類編程,希望通過面向接口編程來降低程序的耦合。下面分兩種常用場景來示范“面向接口”編程的優勢。
(一)簡單工廠模式
有一個場景,假設程序中有個Comupter類需要組合一個輸出設備,現在有兩個選擇:直接讓Comupter該類組合一個Printer屬性,或者讓Comupter組合一個Output屬性,那么到底采用哪種方式更好呢?
假設讓Computer組合一個Printer屬性,如果有一天系統需要重構,需要使用BetterPrinter來代替Printer,於是我們需要打開Computer類源代碼進行修改。如果系統中只有一個Computer類組合了Printer屬性還好,如果系統中有100個類組合了Printer屬性,甚至1000個,10000個……將意味着我們要打開100個、1000個、10000類進行修改,這是多么大的工作量!
為了避免這個問題,我們讓Comupter組合一個Output屬性,將Comupter類與Printer類完全分離。Computer對象實際組合的是Printer對象,還是BetterPrinter對象,對Computer而言完全透明。當Printer對象切換到BetterPrinter對象時,系統完全不受影響。下面是這個Computer類定義的代碼。
public class Computer
{
private Output out;
public Computer(Output out)
{
this.out = out;
}
//定義一個模擬獲取字符串輸入的方法
public void keyIn(String msg)
{
out.getData(msg);
}
//定義一個模擬打印的方法
public void print()
{
out.out();
}
}
上面的Computer類已經完全與Printer類分離開,只是與Output接口耦合。Computer不再負責創建Output對象,系統提供一個Output工廠來負責生成Output對象。這個OutputFactory工廠類代碼如下:
public class OutputFactory
{
public Output getOutput()
{
return new Printer();
}
public static void main(String[] args)
{
OutputFactory of = new OutputFactory();
Computer c = new Computer(of.getOutput());
c.keyIn("瘋狂Java講義");
c.keyIn("輕量級J2EE企業應用實戰");
c.print();
}
}
在該OutputFactory類中包含了一個getOutput方法,該方法返回一個Output實現類的實例,該方法負責創建Output實例,具體創建哪一個實現類的對象由該方法決定(具體由該方法中粗體部分控制,當然也可以增加更復雜的控制邏輯)。如果系統需將Printer改為BetterPrinter實現類,只需要讓BetterPrinter實現Output接口,並改變OutputFactory類中的getOutput方法即可。
下面是BetterPrinter實現類的代碼,BetterPrinter只是對原有的Printer進行簡單修改,以模擬系統重構后的改進。
public class BetterPrinter implements Output
{
private String[] printData = new String[MAX_CACHE_LINE * 2];
//用以記錄當前需打印的作業數
private int dataNum = 0;
public void out()
{
//只要還有作業,繼續打印
while(dataNum > 0)
{
System.out.println("高速打印機正在打印:" + printData[0]);
//把作業隊列整體前移一位,並將剩下的作業數減1
System.arraycopy(printData , 1, printData, 0, --dataNum);
}
}
public void getData(String msg)
{
if (dataNum >= MAX_CACHE_LINE * 2)
{
System.out.println("輸出隊列已滿,添加失敗");
}
else
{
//把打印數據添加到隊列里,已保存數據的數量加1。
printData[dataNum++] = msg;
}
}
}
上面的BetterPrinter類也實現了 Output接口,因此也可當成Output對象使用,於是我們只要把OutputFactory工廠類的getOutput方法中粗體部分改為如下代碼:
return new BetterPrinter();
再次運行前面的OutputFactory.java程序,發現系統運行時已經改為BetterPrinter對象,而不再是原來的Printer對象。
通過這種方式,我們把所有生成Output對象的邏輯集中在OutputFactory工廠類中管理,而所有需要使用Output對象的類只需與Output接口耦合,而不是與具體的實現類耦合。即使系統中有很多類使用了Printer對象,只要OutputFactory類的getOutput方法來生成Output對象是BetterPrinter對象,則它們全部都會改為使用BetterPrinter對象,而所有程序無需修改,只需要修改OutputFactory工廠的getOutput的方法實現即可。
(二)命令模式
考慮這樣一種場景:某個方法需要完成某一個行為,但這個行為的具體實現無法確定,必須等到執行該方法時才可以確定。具體一點:假設有個方法需要遍歷某個數組的數組元素,但無法確定在遍歷數組元素時如何處理這些元素,需要在調用該方法時指定具體的處理行為。
這個要求看起來有點奇怪:這個方法需要不僅要普通數據可以變化,甚至還有方法執行體也需要變化,難道我們能把“處理行為”作為一個參數傳入該方法?
對於這樣一個需求,我們必須把“處理行為”作為參數傳入該方法,這個“處理行為”用編程來實現就是一段代碼。那如何把這段代碼傳入該方法呢?
因為Java不允許代碼塊單獨存在,因此我們使用一個Command接口來定義一個方法,用這個方法來封裝“處理行為”。下面是該Command接口代碼。
public interface Command
{
//接口里定義的process方法用於封裝“處理行為”
void process(int[] target);
}
上面的Command接口里定義了一個process方法,這個方法用於封裝“處理行為”,但這個方法沒有方法體——因為現在還無法確定這個處理行為。
下面是需要處理數組的處理類,在這個處理類中包含一個process方法,這個方法無法確定處理數組的處理行為,所以定義該方法時使用了一個Command參數,這個Command參數負責對數組的處理行為。該類的程序代碼如下。
public class ProcessArray
{
public void process(int[] target , Command cmd)
{
cmd.process(target);
}
}
通過一個Command類,就實現了讓ProcessArray類和具體“處理行為”的分離,程序使用Command接口代表了對數組的處理行為。Command接口也沒有提供真正的處理,只有等到需要調用ProcessArray對象process方法時,才真正傳入一個Command對象,才確定對數組的處理行為。
下面程序示范了對數組的兩種處理方式:
public class TestCommand
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
//第一次處理數組,具體處理行為取決於PrintCommand
pa.process(target , new PrintCommand());
System.out.println("------------------");
//第二次處理數組,具體處理行為取決於AddCommand
pa.process(target , new AddCommand());
}
}
下面分別是PrintCommand類和AddCommand類的代碼。
public class PrintCommand implements Command
{
public void process(int[] target)
{
for (int tmp : target )
{
System.out.println("迭代輸出目標數組的元素:" + tmp);
}
}
}
public class AddCommand implements Command
{
public void process(int[] target)
{
int sum = 0;
for (int tmp : target )
{
sum += tmp;
}
System.out.println("數組元素的總和是:" + sum);
}
}
對於PrintCommand和AddCommand兩個實現類而言,實際有意義的部分就是process(int[] target)方法,該方法的方法體就是傳入ProcessArray類里process方法的“處理行為”,通過這種方式就可實現process方法和“處理行為”的分離。