什麼是OO?物件導向與繼承
續上次聊完的物件導向與封裝,這次我們要討論的是繼承 (Inheritance)。
繼承是物件導向的第二元素,也是 OO 中的一把雙面刃,設計的好壞其實直接影響了程式之後是否更易於維護。
按照慣例,我先了找了個維基百科,讓我們來看看物件導向中的繼承又是指什麼 ?
繼承是物件導向軟體技術當中的一個概念。如果一個類別 B「繼承自」另一個類別 A,就把這個 B稱為「A 的子類別」,而把 A稱為「B 的父類別」也可以稱「A 是 B 的超類」。繼承可以使得子類別具有父類別的各種屬性和方法,而不需要再次編寫相同的程式碼。(撰自: 維基百科)
OK ! 維基百科的解釋就是夠繞口,我喜歡 (這樣我寫的文章才有意義)。
現實世界中,物件多數會與其他物件具有一些相似的特性。如是之前提到的杯子,可以是聞香杯、馬克杯、茶杯、保溫杯等等。
他們都同時共享了杯子的特性,比如存放液體、可以水洗、喝飲料用、漱口等等。但同時有又不一樣的特性,比如漱口杯我們不會拿來喝水、茶杯我們會用來喝茶水,但通常不會用來喝巧克力等等。
因此我們可以透過繼承這個特性,減少相似類別會重複出現的 code,並讓每個繼承後的單獨類別可以擁有各自的屬性和方法。
從類別的角度,我們來看看以下的 code :
Class Cup { public String name; public int size; public bool withHandler; public Array function manufacture(int size, bool withHandler, String name) { array materialList = [size, withHandler, name]; return materialList; } }
這是我們作為一個杯子物件的大類別,現在我發現在茶杯上有一些屬性是不包含在 Cup 中的,但同時原來的屬性也是需要被用到時 ; 我們會希望用兩者都可兼得,這時我們就可以用到繼承:
Class TeaCup extends Cup { public bool forTeaOnly; public String country; }
在上面,TeaCup 繼承自 Cup,所以 TeaCup 會有 Cup 的所有屬性與方法,可以有效減少重複的 code。同時,又為 TeaCup 增加兩個屬性,這些屬性不屬於它的父類別 (Cup),而只屬於 TeaCup 。
所以當我們要新增其他類別來同時繼承自 Cup 時,新的類別同樣只能用到 Cup 原有的屬性與方法,而不會拿到 TeaCup 的屬性與方法。
繼承是能有效的為我們減少相似的 code,同時為差異化提出可行的解決方法。
同樣地,當我們要新增一個類別 ABCCup 來繼承 TeaCup 時,ABCCup 會同時拿到父類別 (TeaCup) 與其父類別 (Cup) 的屬性。
那回過頭來,為什麼在一開始我會說這是一把雙面刃呢 ?
因為同時我們要面對高耦合性的問題。
在繼承類別中,繼承到的屬性與方法會愈來愈多,導至在修改某功能時,會同時影響他的子類別。說白話就是,當 Cup 類別的 name 屬性改名為 names 時,TeaCup 原來是繼承 name 的,在 code上 new 出來的所有屬性都會需要改掉,如下 :
// Original TeaCup newCup = new TeaCup(); newCup.name = "aaa"; // After Changed TeaCup newCup = new TeaCup(); newCup.names = "aaa";
如果我們在多個地方都有 new 出一個 TeaCup 時,以上的問題就會變得有點麻煩,嗯,很麻煩。這代表我們需要改動的地方會非常多,只要有一處我們沒有修正,程式就會出 Bug,這就是一種高耦合的表現。
但能不能透過設計減少這事情的發生?可以的,先讓我們回到封裝。我們可以透過對屬性的封裝來減少修改屬性帶來的問題,如下 :
Class Cup { private String name; private int size; private bool withHandler; public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setSize(int size) { this.size = size; } public int getSize() { return this.size; } public void setWithHandler(bool withHandler) { this.withHandler = withHandler; } public bool getWithHandler() { return this.withHandler; } public Array function manufacture(int size, bool withHandler, String name) { array materialList = [size, withHandler, name]; return materialList; } }
我們可以只透過 Getter, Setter 來存取我們的屬性,而不直接使用屬性本身 (private)。這種做法本身就是把屬性封裝起來,再給外部使用。因此當我們在使用時,不需要再了解到方法以外的事情,我們只需要調用方法 ! 這能大大地降低耦合性。當類別內部的屬性需要改變時,亦不會影響到外部呼叫方法的程式。
最後,我們來回顧一下,OO 中的繼承能為我們增加很大的方便性,減少重複且需要維護的程式碼。但是我們要好好思考甚至依靠一些原則來幫忙我們設計想要的類別與其繼承,否則將會讓程式變得難以維護。
下一集,最終回 OO 三本柱之多型。