介面體現的是一種規範和實現分離的設計哲學,充分利用介面可以極大的降低程式中各個模塊之間的耦合,提高系統的可維護性以及可擴展性。 ...
介面體現的是一種規範和實現分離的設計哲學,充分利用介面可以極大的降低程式中各個模塊之間的耦合,提高系統的可維護性以及可擴展性。
因此,很多的軟體架構設計理念都倡導“面向介面編程”而不是面向實現類編程,以期通過這種方式來降低程式的耦合。
但是在討論這些之前,我們先要搞清楚一個問題:
介面還是抽象類?
為什麼會有這個問題,因為在某些情況下,介面和抽象類很像:
- 介面和抽象類都不能被實例化,它們都位於繼承樹的頂端,用於被其它類實現或者繼承。
- 介面和抽象類都可以包含抽象方法,實現介面或者繼承抽象類的普通子類都必須實現這些抽象方法。
介面作為系統和外界交互的視窗,體現的是一種規範。規定了實現者必須向外提供哪些服務,調用者可以調用哪些服務。當在一個程式中使用介面時,介面是多個模塊之間的耦合標準;當在多個應用程式之間使用介面時,介面是多個程式之間的通信標準。
而抽象類則不同,它作為多個子類的共同父類,體現的是一種模板式設計。它可以作為系統實現的中間產品,但是必須要進行完善才能成為最終產品。
除此之外,介面和抽象類在用法上也存在差別:
- 介面只能包含抽象方法和預設方法,不能為普通方法提供方法實現;抽象類則可以包含普通方法。
- 介面中不能定義靜態方法;抽象類可以。
- 介面不能定義普通成員變數;抽象類可以。
- 介面不包含構造器;抽象類可以包含構造器,但並不是用於創建對象,而是用於讓其他子類調用這些構造器來完成屬於抽象類的初始化操作。
- 一個類只能有一個直接父類,包括抽象類;但一個類可以直接實現多個介面,通過實現多個介面可以彌補單繼承的不足。
下麵介紹兩種常見的應用場景來示範面向介面編程的優勢。
簡單的工廠模式
有這樣一個場景:程式中的每一個Computer類都需要集成一個Printer類,以完成設備的輸入輸出,示例如下:
public class Computer{
public static void main(String []args){
Printer p = new Printer();
p.getMsg("Amos");
p.getMsg("H's blog");
p.printMsg();
}
}
class Printer{
private String msg = "";
public void getMsg(String message){
msg += message;
}
public void printMsg(){
System.out.println(msg);
}
}
但是,當我們在重構代碼的時候,需要使用BetterPrinter類以替換Printer類時,如果只有一個類集成了Printer那麼還好,但是如果有成百上千個類集成了這個類呢?那麼工作量將會是巨大的!
為瞭解決這個問題,我們可以使用介面實現工廠模式,讓Computer類集成一個PrinterFactory類,將Computer類和Printer類的實現完全分隔開來。當從Printer類切換到BetterPrinter類時對系統完全沒有影響。
示例如下:
class Computer{
private Out p;
public Computer(Out printer){
this.p = printer;
}
public void keyIn(String msg){
p.getMsg(msg);
}
public void printMsg(){
p.printMsg();
}
}
class Printer implements Out{
private String msg = "";
public void getMsg(String message){
msg += message;
}
public void printMsg(){
System.out.println(msg);
}
}
interface Out{
String mes = "";
void getMsg(String message);
void printMsg();
}
public class PrinterFactory{
public Out out(){
return new Printer();
}
public static void main(String[] args){
PrinterFactory pf = new PrinterFactory();
Computer c = new Computer(pf.out());
c.keyIn("Amos");
c.keyIn("H's blog");
c.printMsg();
//output AmosH's blog
}
}
該實現使用工廠類用來生產Printer對象實例,而Computer類則只接收工廠類返回的對象用以初始化,分隔了和Printer對象實現之間的耦合。當系統需要使用BetterPrinter類替換Printer類時,只要BetterPrinter類實現了Out介面,然後在PrinterFactory工廠類中修改產生的對象即可。
下麵是替換示例:
class Computer{
private Out p;
public Computer(Out printer){
this.p = printer;
}
public void keyIn(String msg){
p.getMsg(msg);
}
public void printMsg(){
p.printMsg();
}
}
class BetterPrinter implements Out{
//全新的列印類,限制只能存儲5次輸入
private String msg = "";
private int count = 5;
public void getMsg(String message){
msg += message;
count--;
if(count==0){
printMsg();
count = 5;
}
}
public void printMsg(){
System.out.println(msg);
msg="";
count = 5;
}
}
interface Out{
String mes = "";
void getMsg(String message);
void printMsg();
}
public class PrinterFactory{
public Out out(){
return new BetterPrinter();
}
public static void main(String[] args){
PrinterFactory pf = new PrinterFactory();
Computer c = new Computer(pf.out());
c.keyIn("Amos");
c.keyIn("H's ");
c.keyIn("blog ");
c.keyIn("Wel");
c.keyIn("come");
//output AmosH's blog Welcome
c.keyIn("Amos");
c.keyIn("H's ");
c.keyIn("blog ");
c.keyIn("Wel");
c.keyIn("come");
//output AmosH's blog Welcome
c.keyIn("end this");
c.printMsg();
//output end this
}
}
命令模式
考慮這樣一個問題:在某些情形下,某個方法需要完成某一個特殊的行為但這個實現無法確定,必須在調用該方法時指定具體的處理行為。
這就意味著,我們必須把“處理方法”作為參數傳入該方法,這個“處理行為”用編程實現來說就是一段代碼,那麼如何把代碼傳入該方法呢?
我們可以使用Lambda表達式作為處理方法傳入,但是當涉及一些比較複雜的處理行為時,這往往是不夠的。
我們可以考慮使用Command介面定義一個方法,用這個方法來封裝“處理行為”。以下以遍歷一個數組作為示範:
public class Test{
public static void main(String[] args){
int[] target = {1,-1,10,-20};
Process pc = new Process();
pc.process(target,new PrintCmd());
//output 當前元素為正數1
//output 當前元素為負數-1
//output 當前元素為正數10
//output 當前元素為負數-20
System.out.println("=======分割線========");
pc.process(target,new AddCmd());
//output 數組的和為-10
}
}
interface Command{
void process(int[] target);
}
class Process{
public void process(int[] target,Command cmd){
cmd.process(target);
}
}
class PrintCmd implements Command{
public void process(int[] target){
for(int i:target){
if(i<0){
System.out.println("當前元素為負數"+i);
}else{
System.out.println("當前元素為正數"+i);
}
}
}
}
class AddCmd implements Command{
private int count;
public void process(int[] target){
for(int i:target){
count += i;
}
System.out.println("數組的和為"+count);
}
}
對於PrintCmd和AddCmd而言,真正有用的就是process方法,該方法體傳入Process的實例中用以處理數組。實現了process()方法和“處理方法”的相分離。