TIL: 重構程式碼的原則
OO 是為了讓程式容易改,不是設計漂亮。紀錄 Move Method、Switch Smell 兩個Bad Code,以及什麼時候不需要做物件導向。
最近 OOAD 課講重構原則,整理幾個覺得很有用的觀念。
重點
物件導向的主要目的,是讓程式碼可以輕鬆地維護、改變和演化。不是為了讓設計看起來漂亮,也不是為了效能,好的架構不等於好的效能。
課堂中老師說到:不要 Over Design。如果一段程式碼幾乎沒有變動需求,硬要轉成 OO 基本上是浪費時間。重構和設計模式是工具,用在對的地方才有價值。
Move Method
A method should be on the object whose data it uses.
如果一個方法大量使用另一個 class 的資料,那它應該搬過去。
// 問題:OrderPrinter 的 print() 幾乎都在用 Order 的資料class OrderPrinter { void print(Order order) { System.out.println(order.getCustomer()); System.out.println(order.getTotal()); // ... }}// 搬過去比較自然class Order { void print() { System.out.println(this.customer); System.out.println(this.total); }}判斷的問題可以問自己:這個 class 真的需要知道這件事嗎? 如果不需要,就搬。搬移本身就是重構,不需要改任何邏輯。
Switch Smell
It is bad to do a switch based on an attribute of another object.
// 問題:根據 animal.type 決定行為,邏輯分散在外面switch (animal.getType()) { case "dog": animal.bark(); break; case "cat": animal.meow(); break;}問題是每次新增一種動物,就要找到所有 switch 去加 case,容易漏改。
解法是用繼承把行為封裝進去:
abstract class Animal { abstract void speak();}class Dog extends Animal { void speak() { System.out.println("Woof"); } }class Cat extends Animal { void speak() { System.out.println("Meow"); } }// 呼叫端只需要animal.speak();新增動物只要加一個子類別,不用動任何 switch。
繼承的限制
An object cannot change its class during its lifetime.
如果物件的狀態或行為會在執行期間改變,繼承就會卡住:
Movie m = new NewReleaseMovie("Inception");// 一個月後,它變成舊片了...// 沒辦法直接把 m 的 class 換掉,只能 clone & destroy// 但其他地方還持有 m 的 reference,很容易出問題這時應該改用 Strategy Pattern:把「會變的行為」抽成獨立物件,讓主物件持有它。
class Movie { private Price price; void setPrice(Price p) { this.price = p; } double getCharge(int days) { return price.getCharge(days); }}// 變成舊片,只要換 price 物件就好movie.setPrice(new RegularPrice());主物件不換,策略換。
這樣就不是 code reuse,而是 pattern reuse 了。
繼承共享的是程式碼本身;Strategy 共享的是解題的思路。後者在面對需求變動時彈性高很多。
之前有一篇筆記是從租片系統實作角度寫 Strategy Pattern,可以對照著看。