Showing posts with label object oriented design. Show all posts
Showing posts with label object oriented design. Show all posts

12.07.2014

Java的十個物件導向設計原則

對於開發者而言,想必大家都知道design pattern的重要性;但這篇不是要講design pattern,我們要來看看物件導向程式設計的基本原則,其中包含了SOLID(Single responsibility, Open-closed, Liskov substitution, Interface segregation 和 Dependency inversion),這些基本原則可以幫助你寫出更簡潔、易維護、低耦合的系統。

不要自我重覆(DRY:Don't Repeat Yourself)

不要寫出重覆的程式(功能)!若有一部份的程式出現在不同地方,你可以考慮使用抽象化(Abstraction: 將相同的邏輯往parent搬)或委派(Delegation:將相同的任務委派給另一個物件,或另一個method完成),若有hard-code value重覆出現,則使用public final取代之,藉此可以提升維護性
這個原則很重要,但要小心不能濫用,這裡的程式並不是指程式碼,而是指功能。假設我們使用同樣的程式來驗證資料A以及資料B,雖然現在A及B有著相同格式,若我們使用委派,將相同的驗證抽成一個共用method,代表我們認為A和B的格式永遠不會變動,一旦未來其中一個格式有變動,改了驗證程式就會造成另一個資料驗證問題(若沒有Unit test特別容易發生)。因此要特別注意,這個原則適用在相同的功能而不是相似的程式


將變動封裝起來(Encapsulate Changes)

軟體開發中唯一不變的就是”變動”,變動可能來自於需求的改變、使用不同的工具等,要減少變動影響範圍,我們要將可能會變動的程式封裝起來。例如要發動車子,內部有很多零件必需協同和作才能完成,但用戶不需要知道這些細節,他們只需要將鑰匙轉動即可。由於封裝將細節隱藏起來,只提供主要介面供使用者驅動,若內部實作細節有任何異動,用戶端程式可以不用做任何異動。封裝簡單說就是將變數及method儘可能private化,至於要怎麼判斷程式碼是否有做到封裝,依我的經驗,若有段程式從物件A get不同參數值或呼叫A的不同method,很可能這段程式是屬於A的責任,你已經違反了封裝的概念。不要小看封裝,若你的實作散落各地,實作內容變動愈大,你所要修改的類別可能愈多

開放關閉設計原則(Open Close Design Principle)

有點難理解的原則,又開放又關閉的!其實指的是不同東西,開放指的是類別及方法要能夠擴充(加新功能或不同實作),關閉指的是關閉修改(refactoring例外)。理想上當你要加入新功能,應該引入新的類別、方法或變數,而不是去修改既有已經完成測試的程式,這可以避免修改造成的regression bug。
但要如何使程式能夠擴充呢?基本上我們可以運用多型(polymorphism),藉由相同介面(interface或super class),讓我們可以在擴充行為時不影響client端以及既有的程式

單一責任原則(Single Responsibility Principle)

責任指的是變動的理由,一個類別應該只有一個變動的理由例如一個類別負責編輯報表以及列印報表,這個類別就有二個變動的理由;一個是資料的異動,另一個是報表的格式異動。將二個不同責任綁在一起,除了會造成閱讀困難外,也會使其不好維護,畢竟我們不能保證修改其中一個功能時,對另一個功能不會造成任何影響。


相依性注入或反轉原則(Dependency Injection or Inversion Principle)

相信有使用Spring Framework的都對這個原則不漠生,一般我們要使用一個物件可能會直接new 一個instance,但DI告訴我們不要這樣,你應該動態注入相依的物件,這種方式可以帶來下面二種好處:
  1. 讓我們在撰寫測試時可以利用mock object,讓我們專注在該測的功能上
  2. 可藉由修改設定檔注入不同實作(Polymorphism),而不必修改程式。

傾向使用Composition多於繼承(Favor Composition over Inheritance)

使用繼承我們可以將共同的程式碼移至super class,使用composition我們則是將共同的程式碼移至composed class,在不考慮使用繼承是不是好的設計狀況下,二種差別在於彈性:使用繼承我們無法動態變動實作,使用composition,我們可以藉由polymorphism,讓不同實作有相同介面,藉由執行期的相依性注入抽換不同實作,任何時候我們都可以切換不同的實作且不用改程式。

介面分離原則(ISP:Interface Segregation Principle)

這個原則指的是client不應實作一個他不提供的功能介面會違反這個原則,大多是因為一個介面負責了超過一個功能(違反單一責任原則),而client只需要其中部份功能。在介面的設計上必須特別小心,因為一旦發佈了後,日後所做的任何修改都會使現有的實作無法運行。

Liskov 替換原則(Liskov Substitution Principle)

這個原則指的是子類別的實作必需可以替代父類別!也就是說父類別的實作必需在子類別也能運作LSP和單一責任原則以及介面分離原則有很強的關聯,若一個類別負責許多許責任,子類別一旦無法支援所有行為,就會違反LSP。要遵守LSP,則子類別必須是加強父類別的功能,不能減少。舉個教科書常見的錯誤例子來說:父類別Rectangle定義了長及寬變數,以及取得面積的方法,子類別Square繼承了父類別的長及寬變數以及行為,當我們以polymorphism的角度使用Square類別(Rectangle r = new Square())時,使用者認為他必需設定長及寬來計算面積,但當他設定長=10,寬=5並計算面積時,發現回傳是25(因為正方形四邊都一樣)而不是50!因為polymorphism開發時操作的是super class或interface,因此違反了這個原則可能帶來用戶錯誤的預期

針對介面而非實作(Programming for Interface not implementation)

這個原則其實就是要我們善用polymorphism,定義變數或方法變數或回傳變數時使用介面而非實作可以帶來彈性,當要替換不同實作時,這可以讓你減少異動

委派原則(Delegation Principle)

不要試著在一個類別中做所有的事,把責任委派給該負責的類別。例如hash code和equals method,要比較二個物件是否相同,我們並不會在client端寫程式去比,而是交由二個物件本身自己去比較,這樣的好處是可以減少重覆的程式碼,讓系統更好維護。

你可能對下面主題有興趣:
  1. [OO概念]封裝,繼承,多型
  2. [Java 概念]Interface and abstract class

2.24.2013

[Java 概念]Interface and abstract class

在上一篇介紹了OO的基本概念:封裝,繼承和多型,這篇要來介紹一下Java的介面和抽象類別,他們的差別是Java面試時常見的問題,下面來介紹一下他們的差別,以及使用時機。

介面和抽象類別的差別


  1. 介面本身並不包含實作,他只定義行為,實作介面的類別代表他有此行為。相對地,抽象類別本身可以包含抽象和實體方法,可以提供共同/預設的行為。
  2. Java class可以實作多個介面,但只能繼承一個抽象類別,介面能提供更多的多型支援。
  3. 要實作一個介面,你必需實作其所有行為,一旦行為一多,對於實作類別來說相當痛苦,因此較好的設計是一個介面最多提供1~2個行為,不要設計一個介面包含過多行為;而抽象類別則是一開始就提供了預設的實作。
在前文提到,多型本身可利用介面或繼承達成late-binding,以前的我會搞不太清楚何時用介面,何時用抽象類別(繼承),在這裡分享一下我的心得:

介面和抽象類別的使用時機


  1. 因為Java不支援多重繼承但可以實作多個介面,若你想要更多的多型支援,那就必需使用介面。
  2. IS-A關係,同型態的物件本身的行為相同時,就適合用繼承。至於super class本身需不需要Abstract,則是看情形,我認為大部份都是Abstract為多數,通常Abstract class會定義演算法,而將不同的部份定義為abstract method,由各子類別進行實作,這也就是Template Method pattern。
  3. 介面用於定義支援的行為,譬如:Runnable支援run(),Callable支援call(),當只需定義行為,而每個型態的物件本身的行為不同時,就適合用介面。

你可能對下面主題有興趣:
  1. [OO概念]封裝,繼承,多型
  2. Java的十個物件導向設計原則

2.23.2013

[OO概念]封裝,繼承,多型

最近發現最基本的問題,好像大家反而忽略了,想說來分享一下我對基本OO的封裝,繼承,多型見解。

封裝(Encapsulation):就是把不必要的資訊隱藏(Information Hiding)起來,只把必要的操作開放出去。譬如開車加速,我們只要知道踩油門就好,不需知道細節(內部零件間怎麼協同完成這件事),這樣的好處是物件間或模組間的藕合力(Coupling)低,若今天要完成一件事的細節變了,呼叫者可以完全不受影響,藕合力愈低,單元測試愈好寫啊...當你呼叫一個物件的get,就要小心也許你正在破壞他的封裝!

繼承(Inheritance):我們都知道就是父子關係(IS-A relationship),子類別會繼承父類的方法和屬性,繼承本身並沒有問題,但常會見到誤用的情形!就是開發者忘了IS-A的概念,為了reuse,造成了功能型的繼承!若要reuse且非IS-A關係,應用Composition來達成,把共同的部份移至composed class,把要做的事delegate另一個物件完成!

多型(Polymorphism):延申自繼承(Inheritance)或介面(Interface),指的就是不同型態的物件,定義相同的操作介面,由於被呼叫者(Callee)有著相同介面,呼叫者並不用指定特定型別,只需針對介面進行操作,實際執行的物件則在runtime決定,藉此增加程式碼的彈性。
你可能對下面主題有興趣:
  1. [Java 概念]Interface and abstract class
  2. Java的十個物件導向設計原則