千面人


以 這是哪種東西? 最後的範例來說,如果今天你想要開發一組程式庫,其中有個功能(方法)要能撥打各式手機,你不知道會傳入的到底是Cameraphone物件,或者是MP3Cellphone物件,如果你這麼開發,顯然有些不智:
public class PhoneUtil {
    public void dial(Cameraphone 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功能,所以你可能繼承它再定義CameraMP3Cellphone:
public class CameraMP3Phone extends MP3Cellphone {
    private Camera carema;

    public void setCamera(Camera carema) {
        this.carema = carema;
    }

    public Picture take() {
        ....
    }
}

也有可能是繼承已有照相功能的CameraPhone來增加MP3的功能:
public class MP3CameraPhone extends CameraCellphone {
    private MP3Player player;
   
    public void setMP3Player(MP3Player player) {
        this.player = player;
    }

    public void play() {
        player.play();
    }
}

視 需求而定,也許你會很容易採其中一個方案,也許會掙扎著不知道該採哪一個方案,但無論如何,現在再來另一個需求,如果程式庫中的另一個要求是,可以對所有 具MP3功能的手機進行MP3撥放,以上兩個設計方式,都會造成程式庫設計上的困擾,因為無論是CameraMP3Phone或是 MP3CameraPhone,都「不是一種」MP3Player,如果硬要設計,你也許只能寫出這樣的程式:
public class PhoneUtil {
    public void play(CameraMP3Phone phone) {
        phone.play()
    }
    public void play(MP3CameraPhone phone) {
        phone.play()
    }
}

繼承需謹謓使用,這就是一個例子,如果你的程式出現了類似這種冏境,就是思考重構(Refactor可能性了,也許你可以這麼設計:
public interface ICamera {
    Picture take();
}

public interface IMP3Player {
    void play();
}

你使用介面定義ICamera與IMP3Player,並讓Camera及MP3Player各實作介面:
public class Camera implements ICamera {
    ...
}

public class MP3Player implements IMP3Player {
    ...
}

如果要實作照相手機,則可以如下:
public class CameraCellphone extends Cellphone implements ICamera {
    private ICamera carema;

    public void setCamera(ICamera 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 ICamera, IMP3Player {
    private ICamera carema;
    private IMP3Player player;
   
    public void setCamera(ICamera 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(ICamera carema) {
        return carema.take();
    }
}

這樣的設計顯然有彈性多了。這也是多型的一種運用,你不用知道實際物件是哪一種,而只需要知道物件有實作哪個介面的行為,你就可以透過介面上所定義的行為來操作實際的物件動作。

也許你一開始就掌握了需求,所以有可能事先就考慮定義介面的必要,但也許你事先掌握的需求不足,所以作出 是哪種東西? 的 設計也是有可能的,事前的需求分析之所以重要,就在這邊可以看出來,事前需求分析作的好,可以減少日後修改程式碼的可能性,但需求分析需要經驗與技巧,非 一蹴可及,這時在需求增加或變動時,就要在重構程式時多所費心了,無論如何,切莫在有重構的可能性時,卻遷就既有的架構硬K出程式來。