重構 - ch12 - 處理繼承
提升方法(Pull Up Method)
移除重複的程式碼相當重要,如果你有一個函式,而他們重複出現在兩個甚至多個地方,那他很有可能就會變成滋養bug的溫床。
你將很有可能在某天改了其中一個,而遺忘了剩下的。
before
1 2 3 4 5 6 7 8 9 10 11 12 13
| class father {}
class son1 extends father { public function A(); }
class son2 extends father { public function A(); }
|
after
1 2 3 4 5 6 7 8 9 10
| class father { public function A(); }
class son1 extends father {}
class son2 extends father {}
|
下移方法(Push Down Method)
提升方法的逆操作。
當你的方法只與某一個子類別有關的時候,或是當需求變更,使得你在不同子類別需要不同的實作的時候。
before
1 2 3 4 5 6 7 8 9 10
| class father { public function only_for_son1(); }
class son1 extends father {}
class son2 extends father {}
|
after
1 2 3 4 5 6 7 8 9 10
| class father {}
class son1 extends father { public function only_for_son1(); }
class son2 extends father {}
|
提升欄位(Pull Up Field)
將重複的欄位提升到超類別有助於減少兩種重複,第一個是可以移除重複的宣告,再者我們還有機會將子類別中會使用到這些欄位的行為移到超類別。
before
1 2 3 4 5 6 7 8 9 10 11 12 13
| class father {}
class son1 extends father { public $name; }
class son2 extends father { public $name; }
|
after
1 2 3 4 5 6 7 8 9 10
| class father { public $name; }
class son1 extends father {}
class son2 extends father {}
|
下移欄位(Push Down Method)
提升欄位的逆操作。
如果某個欄位只在某個子類別會被使用,則將該欄位下移到子類別中。
before
1 2 3 4 5 6 7 8 9 10
| class father { public $field_only_for_son1; }
class son1 extends father {}
class son2 extends father {}
|
after
1 2 3 4 5 6 7 8 9 10
| class father {}
class son1 extends father { public $field_only_for_son1; }
class son2 extends father {}
|
提升建構式內文(Pull Up Cobstructor Body)
這邊有點類似提升方法(Pull Up Method),當多個子類別的建構子裡面有相同的過程的時候,我們將相同的過程移至父類別的建構子。
before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class father {}
class son1 extends father { public function __construct() { } }
class son2 extends father { public function __construct() { } }
|
after
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class father { public function __construct() { } }
class son1 extends father { public function __construct(){ parent::__construct(); } }
class son2 extends father { public function __construct(){ parent::__construct(); }; }
|
將型別代碼換成子類別(Replace Type Code with Subclasses)
子類別有兩件事情具有一些優勢
- 如果你的程式會隨著行別代碼的值去調整行為邏輯,則子類別可以讓我們使用多型來處理行為邏輯。
- 我們可以在建立子類別時,順便加入一些驗證邏輯,以確保欄位的值不會脫離我們的期待。
before
1 2 3 4 5 6
| public function createEmployee(string $name, string $type) { return new Employee($name, $type); }
|
after
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public function createEmployee(string $name, string $type) { swtich ($type) { case 'engineer': return new Engineer($name); break; case 'PM': return new PM($name); break; default: return new DefaultEmployee($name) break; } }
|
移除子類別(Remove Subclasses)
使用子類別代替行別代碼雖然是一個很棒的好方法,但隨著系統的演進,子類別的差異可能會在演進過程中變得很少或是沒有差異,使得你可能需要花更多的時間去理解程式,這種狀況是我們不樂見的。
before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Person { public function getGenderCode() { return 'x'; } }
class Male extends Person { public function getGenderCode() { return 'M'; } }
class Female extends Person { public function getGenderCode() { return 'F'; } }
|
after
1 2 3 4 5 6 7 8 9 10 11
| class Persion { private $gender_code; public function getGenderCode() { return $this->gender_code; } }
|
如果有兩個類別職責上做了類似的事情,且有相似的部分,我們可以利用「繼承」來將相似的東西提取成超類別。
before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Engineer { private $name; public function __construct(string $name); public function getName(); public function writeCode(); }
class PM { private $name; public function __construct(string $name); public function getName(); public function writeUserStory(); }
|
after
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Employee { private $name; public function __construct(string $name); public function getName(); }
class Engineer extends Employee { public function __construct(string $name) { parent::__construct($name); } public function writeCode(); }
class PM extends Employee { public function __construct(string $name) { parent::__construct($name); } public function writeUserStory(); }
|
折疊類別階層(Collapse Hierarchy)
當我們在繼承的關係裡面發現,父類別及其子類別的差異性已經小到沒有必要將他們拆開時,我們將繼承關係合併。
before
1 2 3 4 5
| class Employee {...}
class Engineer extends Employee {...}
|
after
將子類別換成委託類別(Replace Subclass with Delegate)
繼承會伴隨著一些缺點,他就像是只能打出一次的撲克牌,如果類別演變的理由不只一個,繼承本身只能選擇其中一種變異線。例如:如果我想依照人類的年齡跟收入去撰寫相符的行為,我可以使用「年情人」或是「老人」類別,貨我可以使用「富人」或「窮人」類別,但我無法同時使用這兩組。
另外一個問題是,繼承本身代表著與父類別的緊密關係,一但我修改了父類別,他會很容易造成子類別的行為被破壞。
委託可以處理這兩種問題,我可以根據不同的理由,將工作委託給許多不同的類別。委託是物件之間的一般關係,所以介面會更明確,其耦合程度會遠小於子類別化。
將超類別換成委託類別(Replace Superclass with Delegate)
如果超類別的功能只適用於某些子類別,而有些子類別不是用,但子類別本身的特性又可以使用超類別的所有功能,這會造成一些問題,像是這種狀況就不應該藉由繼承來使用超類別的功能。