抽象類和介面的簡單例子
為了說明抽象類和介面的使用是如何使用的,下面我們來新建一個酒店預訂專案。使用者輸入代表日期的字串如“2013;2;3”,系統輸出成功預訂資訊以及預定日期如“Hotel reserved at 2013/2/3”。我們用DateTransfer物件將日期字串轉換為標準日期,Hotel物件接收DateTransfer物件轉換好的標準日期並打印出成功預訂資訊。
首先我們來處理日期字串的轉換,新建一個測試類DateTransferTest ,並在其中新建一個測試。
public class DateTransferTest {
@Test
public void shouldGetDateRight() throws Exception {
//Given
String input = "2013;2;3";
DateTransfer dateTransfer = new DateTransfer(input);
//When
Calendar calendar = dateTransfer.getDate();
//Then
assertThat(calendar.get(Calendar.YEAR), is(2013));
assertThat(calendar.get(Calendar.MONTH), is(2));
assertThat(calendar.get(Calendar.DAY_OF_MONTH), is(3));
}
}
接下來我們來新建DateTransfer 類,並實現它的getDate()方法。public class DateTransfer {
private String input;
public DateTransfer(String input) {
this.input = input;
}
public Calendar getDate() {
Calendar cal = Calendar.getInstance();
String[] dates = input.split(";");
cal.set(Calendar.YEAR, Integer.parseInt(dates[0]));
cal.set(Calendar.MONTH, Integer.parseInt(dates[1]));
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dates[2]));
return cal;
}
}
此時測試通過,接下來我們新建一個測試類HotelTest,並在其中新建一個測試
public class HotelTest {
private Hotel hotel = new Hotel();
@Test
public void shouldGetRightOutput() throws Exception {
//Given
String input = "2013;8;10";
//When
hotel.setDateTransfer(new DateTransfer(input));
//Then
String output = hotel.reserve();
assertThat(output, is("Hotel reserved at 2013/8/10"));
}
}
我們新建Hotel類,並實現它的setDateProvide()方法和reserve()方法。public class Hotel {
private DateTransfer dateTransfer;
public void setDateTransfer(DateTransfer dateTransfer) {
this.dateTransfer = dateTransfer;
}
public String reserve() {
Calendar cal = dateTransfer.getDate();
return "Hotel reserved at " + cal.get(Calendar.YEAR) + "/" +
cal.get(Calendar.MONTH) + "/" + cal.get(Calendar.DAY_OF_MONTH);
}
}
到此這個酒店預訂專案已經完成了基本的輸入輸出。此時來了新的需求,要求能夠處理"2013-8-10"這種格式的輸入字串。為了能夠處理兩種不同格式的字串,我們需要新建一個DateTransfer來處理"2013-8-10"這種格式的輸入字串。首先新建一個測試類AnotherDateTransferTest ,並在其中新建一個測試。
public class AnotherDateTransferTest {
@Test
public void shouldGetDateRight() throws Exception {
//Given
AnotherDateTransfer anotherDateTransfer = new AnotherDateTransfer("2013-5-13");
//When
Calendar calendar = anotherDateTransfer.getDate();
//Then
assertThat(calendar.get(Calendar.YEAR), is(2013));
assertThat(calendar.get(Calendar.MONTH), is(5));
assertThat(calendar.get(Calendar.DAY_OF_MONTH), is(13));
}
}
接下來我們來新建AnotherDateTransferTest 類,並實現它的getDate()方法。public class AnotherDateTransfer {
private String input;
public AnotherDateTransfer(String input) {
this.input = input;
}
public Calendar getDate() {
Calendar cal = Calendar.getInstance();
String[] dates = input.split("-");
cal.set(Calendar.YEAR, Integer.parseInt(dates[0]));
cal.set(Calendar.MONTH, Integer.parseInt(dates[1]));
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dates[2]));
return cal;
}
}
我們需要在Hotel類中新建一個測試用來測試第二種輸入字串。
@Test
public void shouldGetRightOutputForAnother() throws Exception {
//Given
String input = "2013-8-10";
//When
hotel.setDateTransfer(new AnotherDateTransfer(input));
//Then
String output = hotel.reserve();
assertThat(output, is("Hotel reserved at 2013/8/10"));
}
當我們輸入上面這段程式碼時,第6行的語句是不能通過編譯的,因為Hotel類的setDateTransfer()方法只接受DateTransfer型別的物件。這時我們可以定義另外一種方法來接受AnotherDateTransfer型別的物件,但如果每次新增一個數據型別我都要在Hotel類增加一個新的方法來處理,這樣顯然是不合理的。
我們可以看出,不管是DateTransfer物件還是AnotherDateTransfer物件,我們都要為Hotel物件提供一個getDate()方法。而Hotel物件只關心資料轉換物件能夠提供getDate()方法,並不關心這個資料轉換物件是DateTransfer型別的還是AnotherDateTransfer型別的。因此我們就需要提供一個包含getDate()方法的介面。我們將這個介面命名為IDateTransfer,其中有一個getDate()的抽象方法。
public interface IDateTransfer {
Calendar getDate();
}
然後讓DateTransfer和AnotherDateTransfer均繼承這個介面,這樣Hotel類就可以改為:public class Hotel {
private IDateTransfer iDateTransfer;
public void setDateTransfer(IDateTransfer iDateTransfer) {
this.iDateTransfer = iDateTransfer;
}
public String reserve() {
Calendar cal = iDateTransfer.getDate();
return "Hotel reserved at " + cal.get(Calendar.YEAR) + "/" +
cal.get(Calendar.MONTH) + "/" + cal.get(Calendar.DAY_OF_MONTH);
}
}
這樣我們在Hotel中的兩個測試均可以通過。
通過比較DateTransfer和AnotherDateTransfer這兩個類我們發現,它們的欄位和方法基本上是相同的,除了字串的分割符不一樣,首先我們將分隔符抽取成一個方法,DateTransfer就改為:
public class DateTransfer implements IDateTransfer {
private String input;
public DateTransfer(String input) {
this.input = input;
}
public Calendar getDate() {
Calendar cal = Calendar.getInstance();
String[] dates = input.split(getSplitString());
cal.set(Calendar.YEAR, Integer.parseInt(dates[0]));
cal.set(Calendar.MONTH, Integer.parseInt(dates[1]));
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dates[2]));
return cal;
}
private String getSplitString() {
return ";";
}
}
我們對AnotherDateTransfer進行同樣的重構,此時DateTransfer和AnotherDateTransfer這兩個類除了getSplitString()方法返回的字串不一樣外,其他程式碼都一樣,對待這種重複程式碼我們需要將DateTransfer和AnotherDateTransfer中相同的實現抽取成一個父類。而getSplitString()方法在不同的子類中有不同的實現,因此在父類中我們將其定義為abstract方法。這樣我們的父SDateTransfer 就成為了抽象類。
public abstract class SDateTransfer {
protected String input;
public SDateTransfer(String input) {
this.input = input;
}
public Calendar getDate() {
Calendar cal = Calendar.getInstance();
String[] dates = input.split(getSplitString());
cal.set(Calendar.YEAR, Integer.parseInt(dates[0]));
cal.set(Calendar.MONTH, Integer.parseInt(dates[1]));
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dates[2]));
return cal;
}
protected abstract String getSplitString();
}
上面這種設計在設計模式中叫做模板模式(Template)。
這樣我們就可以消除DateTransfer和AnotherDateTransfer中的重複程式碼。
通過上面的過程我們總結一下:介面可以看做是一種協議,只要繼承自IDateTransfer的物件都可以被Hotel類呼叫。而抽象類可以看做是一種模板,它包含DateTransfer和AnotherDateTransfer中相同的欄位和方法。