1. 程式人生 > >抽象類和介面的簡單例子

抽象類和介面的簡單例子

      為了說明抽象類和介面的使用是如何使用的,下面我們來新建一個酒店預訂專案。使用者輸入代表日期的字串如“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();
}
      然後讓DateTransferAnotherDateTransfer均繼承這個介面,這樣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中的兩個測試均可以通過。

      通過比較DateTransferAnotherDateTransfer這兩個類我們發現,它們的欄位和方法基本上是相同的,除了字串的分割符不一樣,首先我們將分隔符抽取成一個方法,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進行同樣的重構,此時DateTransferAnotherDateTransfer這兩個類除了getSplitString()方法返回的字串不一樣外,其他程式碼都一樣,對待這種重複程式碼我們需要將DateTransferAnotherDateTransfer中相同的實現抽取成一個父類。而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)。

   這樣我們就可以消除DateTransferAnotherDateTransfer中的重複程式碼

通過上面的過程我們總結一下:介面可以看做是一種協議,只要繼承自IDateTransfer的物件都可以被Hotel類呼叫。而抽象類可以看做是一種模板,它包含DateTransferAnotherDateTransfer中相同的欄位和方法。