<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
摘要:你想寫出無需改變源代碼就可以進行擴展的程序嗎?這篇文章介紹了如何使用interface和動態class載入來創建高擴展性的系統。從中你也可以學習到如何令其他的編程者和用戶不需你的源代碼,就可以對程序進行擴展。首先我們看一個沒有使用interface和動態載入的簡單例子,然後再講述一個動態載入類的例子,這些類是由一個文件或者數據庫的表格中讀取的。
你曾經開發過一個要經常添加新功能的應用嗎?在下面的例子中,市場部將會為每個顧客提供各種各樣的價格處理。你的程序需要處理這些新的需求,你也必須讓用戶可以定制你的軟件而無需改變源代碼。
你可以做到避免修改現有的代碼並且測試加入的新功能嗎?你可以做到無需重新編譯全部的東西來加入新的類嗎?答案是可以的,你可能已經猜到了,就是使用interface和動態類載入。
要說明一下的是,為了說明方便,這裡介紹的類和體系都是經過簡化的。
什麼是interface(接口)?
interface只是描述一個對象是如何被調用的。當你定義了一個接口,你就定義了其它的對象如何使用它。
對於大部分使用Java的人來說,你們可能已經知道接口是什麼東西。但對於那些仍然不清楚的人,我將介紹一些基本的知識,然後創建一些復雜的例子。假如你已經很清楚接口的知識,你可以直接跳到“使用字符串來指定類名字”的部分。
接口的威力
以下的例子說明了接口的威力。假定你的客戶是搞經紀的,他們想讓你建立一個交易的系統。他們的交易是各種各樣的:包括有股票、債券和日用品等等。不同客戶的交易數量也是不一樣的,該數量由客戶稱為pricing plans的東東來定義。
你首先考慮類的設計。主要的類和它們的屬性由客戶來定義,可以是:
Customer(顧客):Name(名字),Address(地址),Phone(電話)和PricingPlan
Trade(交易):TradeType(股票、債券或者日用品),ItemTraded(股票的記號)、NumberOfItemsTraded, ItemPrice, CommissionAmount
PricingPlan:通過一個過程的調用來計算該交易的CommissionAmount
不使用interface的編碼
開始編碼時你可以不使用接口,然後再由該代碼增強其功能。現在,該客戶有兩個標價計劃定義如下:
計劃1:對於常規的顧客,$20/交易
計劃2:一個月中的前10個交易,$15/交易,以後的 $10/交易
Trade對象使用一個PricingPlan對象來計算要收顧客多少傭金。你為每個標價計劃都創建了一個PricingPlan類。對於計劃1,該類稱為PricingPlan20,而計劃2的類則稱為PricingPlan1510。兩個類都通過一個稱為CalcCommission()的過程來計算傭金。代碼如下所示:
類名: PricingPlan20
public double calculateCommission( Trade trade )
{
return 20.0;
}
類名: PricingPlan1510
public double calculateCommission( Trade trade )
{
double commission = 0.0;
if( trade.getCustomer().getNumberOfTradesThisMonth() <= 10 )
commission = 15.0;
else
commission = 10.0;
return commission;
}
以下是在交易中得到傭金的代碼:
public double getCommissionPrice()
{
double commissionPrice = 0.0;
if( getCustomer().getPlanId() == 1 )
{
PricingPlan20 plan1 = new PricingPlan20();
commissionPrice = plan1.calculateCommission( this.getCustomer() );
plan1 = null;
}
else
{
PricingPlan1510 plan2 = new PricingPlan1510();
commissionPrice = plan2.calculateCommission( this.getCustomer() );
plan2 = null;
}
return commissionPrice;
}
使用interface
使用接口的話,將會令上面的例子變得更加簡單。你可以創建PricingPlan的接口,然後定義實現該接口的PricngPlan類:
接口名:IPricingPlan
public interface IPricingPlan {
public double calculateCommission( Trade trade );
}
由於你定義的是一個接口,所以你無需為calculateCommission()定義一個方法體。真正的PricingPlan類將會實現該部分的代碼。接著你就要修改PricingPlan類,第一步是聲明它將會實現你剛剛定義的接口。你只要在PricingPlan類的定義中加入以下代碼就可以:
public class PricingPlan20 extends Object implements IPricingPlan {
在Java中,當你聲明將實現一個接口的時候,你必須實現該接口中的全部方法(除非你要創建一個抽象類,這裡不討論)。因此所有實現IPricingPlan的類都必須定義一個calculateCommission()的方法。該方法的所有標記必須和接口定義的完全一樣,所以它必須接受一個Trade對象,由於我們的兩個PricingPlan類中都已經定義了calculateCommission()方法,因為我們沒有必要作進一步的修改。假如你要創建新的PricingPlan類,你就必須實現IPricingPlan和相應的calculateCommission()方法。
接著你可以修改Trade類的getCommissionPrice()方法來使用該接口:
類名: Trade
public double getCommissionPrice()
{
double commissionPrice = 0.0;
IPricingPlan plan;
if( getCustomer().getPlanId() == 1 )
{
plan = new PricingPlan20();
}
else
{
plan = new PricingPlan1510();
}
commissionPrice = plan.calculateCommission( this );
return commissionPrice;
}
要注重的是,你將PricingPlan變量定義為IPricingPlan接口。你實際創建的對象根據客戶的標價計劃而定。由於兩個PricingPlan類都實現了IPricingPlan接口,所以你可以將兩個新的實例賦給同一個變量。Java實際上並不關心實現該接口的實際對象,它只是關心接口。
使用字符串來指定類名
假定老板告訴你該公司又有兩個新的價格計劃,接著還有更多。這些價格計劃是每交易$8或者$10。你決定要創建兩個新的PricingPlan類: PricingPlan8 和 PricingPlan10。
在這種情況下,你必須修改Trade類來包含這些新的價格計劃。你可以加入更多的if/then/else句子,但這不是一個好方法,假如價格計劃變得越來越多時,代碼將會顯得十分粗笨。另一個選擇是通過Class.forName() 方法來創建PricingPlan實例,而不是通過new。Class.forName()方法可讓你通過一個字符串名字來創建實例,以下就是在Trade類中應用該方法的例子:
類名: Trade
public double getCommissionPrice()
{
double commissionPrice = 0.0;
IPricingPlan plan;
Class commissionClass;
try
{
if( getCustomer().getPlanId() == 1 )
{
commissionClass = Class.forName( "string_interfaces.PricingPlan20" );
}
else
{
commissionClass = Class.forName( "string_interfaces.PricingPlan1510" );