如果你已經定義好某個類別,可以繼承這個類別,自然的,子類別就擁有父類別已定義好的功能,你可以在父類別的基礎上,擴充你想要的功能。
就功能面來說,繼承確實是有以上所提及的作用,然而,就語義來說,繼承具有是一種(is a)的關係,也就是你繼承自一個東西,你就是那種東西,以 繼承了什麼? 中的例子而言,SwordsMan與Magician不僅繼承了Sprite中的程式碼定義,其實,還繼承了是一種的關係,劍士是一種角色,魔法師也是一種角色,也就是說,繼承不單只是繼承父類別的程式定義,還繼承了種類。
在Java這類只支援單一繼承的語言中,是一種的關係更強烈且更具限制性。
假設今天你開發了一個照相機(Camera)類別:
public class Camera {
public Picture take() {
....
}
}
public Picture take() {
....
}
}
現在你想開發一款手機(Cellphone),擁有照相功能,你可能會想,因為你已經有照相機類別了,這個類別已具有照相的功能定義,繼承這個類別再增加撥打電話的功能,如此一來你就不用再重新於手機類別中,定義照相的功能了:
public class Cellphone extends Camera {
public void dial(String number) {
...
}
}
public void dial(String number) {
...
}
}
然而,仔細想想,手機是一種照相機嗎?或許就現階段而言,這可以解決問題,但如果將來,你打算開發一款手機,擁有MP3播放的功能,你想想之後,直接繼承手機好了:
public class MP3Cellphone extends Cellphone {
public void play() {
....
}
}
public void play() {
....
}
}
但如果是低價手機,不能擁有照相機的功能,這樣的需求就使得這種設計不適用了。顯然地,手機不是一種照相機,只是某個手機擁有照相的功能,MP3手機繼承自手機,手機繼承自照相機,那MP3手機會是一種照像機嗎?
未思考是否具有「是一種」的關係,而只是為了繼承某個類別既有的功能來進行擴充,就用了繼承的機制,往往會導致不適當的設計,要不就讓類別擁有了不該有的職責,要不就導致過深的繼承機制,要不就將來需求變更後擴充不易,而使得必須調整程式架構。
如果只是為了擁有某個已定義好的功能,繼承並非最優先的考量,而可以考慮看看複合(Composite)關係。例如,如果你想開發一個手機具有照相功能,也許你可以這麼設計:
public class Cellphone {
private Carema carema;
public void setCarema(Carema carema) {
this.carema = carema;
}
public Picture take() {
return carema.take();
}
public void dial(String number) {
...
}
}
private Carema carema;
public void setCarema(Carema carema) {
this.carema = carema;
}
public Picture take() {
return carema.take();
}
public void dial(String number) {
...
}
}
在這樣的設計之下,手機不會是一種照相機,而只是內建照相機,這是有一個(has a)的關係。現在如果有款手機擁有播放MP3的功能,或許你可以定義一個MP3Player類別,然後如下設計:
public class MP3Cellphone {
private MP3Player player;
public void setMP3Player() {
this.player = player;
}
public void play() {
player.play();
}
public void dial(String number) {
...
}
}
private MP3Player player;
public void setMP3Player() {
this.player = player;
}
public void play() {
player.play();
}
public void dial(String number) {
...
}
}
顯然地,就解決了先前的需求。不過,現在發現,你重複撰寫了dial()方法。這時候,你可以思考,或許可以定義一個CellPhone父類別:
public class Cellphone {
public void dial(String number) {
...
}
}
public void dial(String number) {
...
}
}
如果是照相手機,本質上「是一種」手機內建照相機,所以現在照相手機可以如下設計:
public class CaremaCellphone extends Cellphone {
private Carema carema;
public void setCarema(Carema carema) {
this.carema = carema;
}
public Picture take() {
return carema.take();
}
}
private Carema carema;
public void setCarema(Carema carema) {
this.carema = carema;
}
public Picture take() {
return carema.take();
}
}
就這方面來說,使用繼承時擁有「是一種」關係並不為過。而具有播發MP3功能的手機,則可以如下定義:
public class MP3Cellphone extends Cellphone {
private MP3Player player;
public void setMP3Player() {
this.player = player;
}
public void play() {
player.play();
}
}
private MP3Player player;
public void setMP3Player() {
this.player = player;
}
public void play() {
player.play();
}
}
優先思考利用複合關係,並在適當的時候才應用上繼承,謹慎思考關係到底是「有一個」或「是一種」,對於設計上才會擁有較大的彈性。