|
|
以 這是哪種東西? 最後的範例來說,如果今天你想要開發一組程式庫,其中有個功能(方法)要能撥打各式手機,你不知道會傳入的到底是Caremaphone物件,或者是MP3Cellphone物件,如果你這麼開發,顯然有些不智: public class PhoneUtil { public void dial(Caremaphone phone) { phone.dial(); } public void dial(MP3phone phone) { phone.dial(); } } 利用Java中的方法多載(Overload),雖然可以解決現階段的問題,但如果將來手機的種類增多,勢必要增加更多方法定義。回過頭來思考,功能要求是要能撥打各式手機,傳進來的不就「是一種」手機嗎?也就是一種Cellphone不是嗎?所以你可以這麼設計: public class PhoneUtil { public void dial(Cellphone phone) { phone.dial(); } } 你不用管實際傳入的物件是哪個類別實例,只要知道「是一種」Cellphone就可以了,也就是該物件的類別定義是繼承Cellphone而來,由於所有的Cellphone都會繼承dial()方法這個公開介面,你可以放心的操作所傳入實例的dial()方法。 這就是多型(Polymorphism)的運用範例。所謂多型,可以解釋為,透過同一型態,可參考至不同的物件,透過型態上的公開介面,操作物件上實際的實作。從上面這個例子,你可以看到多型有助於減少程式碼的撰寫量,同時間提昇程式邏輯的清晰度。 就 這是哪種東西? 最後的範例來說,如果你想開發一款更高檔的手機,具備MP3與照相機的功能,MP3Cellphone已具備手機與MP3功能,所以你可能繼承它再定義CaremaMP3Cellphone: public class CaremaMP3Phone extends MP3Cellphone { private Carema carema; public void setCarema(Carema carema) { this.carema = carema; } public Picture take() { .... } } 也有可能是繼承已有照相功能的CaremaPhone來增加MP3的功能: public class MP3CaremaPhone extends CaremaCellphone { private MP3Player player; public void setMP3Player(MP3Player player) { this.player = player; } public void play() { player.play(); } } 視 需求而定,也許你會很容易採其中一個方案,也許會掙扎著不知道該採哪一個方案,但無論如何,現在再來另一個需求,如果程式庫中的另一個要求是,可以對所有 具MP3功能的手機進行MP3撥放,以上兩個設計方式,都會造成程式庫設計上的困擾,因為無論是CaremaMP3Phone或是 MP3CaremaPhone,都「不是一種」MP3Player,如果硬要設計,你也許只能寫出這樣的程式: public class PhoneUtil { public void play(CaremaMP3Phone phone) { phone.play() } public void play(MP3CaremaPhone phone) { phone.play() } } 繼承需謹謓使用,這就是一個例子,如果你的程式出現了類似這種冏境,就是思考重構(Refactor)可能性了,也許你可以這麼設計: public interface ICarema { Picture take(); } public interface MP3Player { void play(); } 你使用介面定義ICarema與IMP3Player,並讓Carema及MP3Player各實作介面: public class Carema implements ICarema { ... } public class MP3Player implements IMP3Player { ... } 如果要實作照相手機,則可以如下: public class CaremaCellphone extends Cellphone implements ICarema { private ICarema carema; public void setCarema(ICarema carema) { this.carema = carema; } public Picture take() { return carema.take(); } } 如果要實作MP3手機,則可以如下: public class MP3Cellphone extends Cellphone implements IMP3Player { private IMP3Player player; public void setMP3Player(IMP3Player player) { this.player = player; } public void play() { player.play(); } } 如果要同時實作具備照相及MP3功能的手機,則可以如下: public class AdvancedPhone extends Cellphone implements ICarema, IMP3Player { private ICarema carema; private IMP3Player player; public void setCarema(ICarema carema) { this.carema = carema; } public void setMP3Player(IMP3Player player) { this.player = player; } public Picture take() { return carema.take(); } public void play() { player.play(); } } 現在你的程式庫可以這麼設計了: public class PhoneUtil { public void dial(Cellphone phone) { phone.dial(); } public void play(IMP3Player player) { player.play(); } } 無論傳入play()方法中的是「哪一種」物件,你都不用管,你只需要傳入的物件,都「有IMP3Player所定義的行為」就可以了。如果現在的需求再增加,必須針對所有具照相功能的手機執行照相功能,則只要增加程式庫的: public class PhoneUtil { public void dial(Cellphone phone) { phone.dial(); } public void play(IMP3Player player) { player.play(); } public Picture take(ICarema carema) { return carema.take(); } } 這樣的設計顯然有彈性多了。這也是多型的一種運用,你不用知道實際物件是哪一種,而只需要知道物件有實作哪個介面的行為,你就可以透過介面上所定義的行為來操作實際的物件動作。 也許你一開始就掌握了需求,所以有可能事先就考慮定義介面的必要,但也許你事先掌握的需求不足,所以作出 是哪種東西? 的 設計也是有可能的,事前的需求分析之所以重要,就在這邊可以看出來,事前需求分析作的好,可以減少日後修改程式碼的可能性,但需求分析需要經驗與技巧,非 一蹴可及,這時在需求增加或變動時,就要在重構程式時多所費心了,無論如何,切莫在有重構的可能性時,卻遷就既有的架構硬K出程式來。 |