重構 - ch12 - 處理繼承
提升方法(Pull Up Method)
移除重複的程式碼相當重要,如果你有一個函式,而他們重複出現在兩個甚至多個地方,那他很有可能就會變成滋養bug的溫床。
你將很有可能在某天改了其中一個,而遺忘了剩下的。
before
| 12
 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
| 12
 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | class father
 {
 public function only_for_son1();
 }
 
 class son1 extends father {}
 
 class son2 extends father {}
 
 
 | 
after
| 12
 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
| 12
 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | class father
 {
 public $name;
 }
 
 class son1 extends father {}
 
 class son2 extends father {}
 
 
 | 
下移欄位(Push Down Method)
提升欄位的逆操作。
如果某個欄位只在某個子類別會被使用,則將該欄位下移到子類別中。
before
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | class father
 {
 public $field_only_for_son1;
 }
 
 class son1 extends father {}
 
 class son2 extends father {}
 
 
 | 
after
| 12
 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
| 12
 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
| 12
 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
| 12
 3
 4
 5
 6
 
 | public function createEmployee(string $name, string $type)
 {
 return new Employee($name, $type);
 }
 
 
 | 
after
| 12
 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
| 12
 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | class Persion
 {
 private $gender_code;
 
 public function getGenderCode()
 {
 return $this->gender_code;
 }
 }
 
 
 | 
如果有兩個類別職責上做了類似的事情,且有相似的部分,我們可以利用「繼承」來將相似的東西提取成超類別。
before
| 12
 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
| 12
 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
| 12
 3
 4
 5
 
 | class Employee {...}
 
 class Engineer extends Employee {...}
 
 
 | 
after
將子類別換成委託類別(Replace Subclass with Delegate)
繼承會伴隨著一些缺點,他就像是只能打出一次的撲克牌,如果類別演變的理由不只一個,繼承本身只能選擇其中一種變異線。例如:如果我想依照人類的年齡跟收入去撰寫相符的行為,我可以使用「年情人」或是「老人」類別,貨我可以使用「富人」或「窮人」類別,但我無法同時使用這兩組。
另外一個問題是,繼承本身代表著與父類別的緊密關係,一但我修改了父類別,他會很容易造成子類別的行為被破壞。
委託可以處理這兩種問題,我可以根據不同的理由,將工作委託給許多不同的類別。委託是物件之間的一般關係,所以介面會更明確,其耦合程度會遠小於子類別化。
將超類別換成委託類別(Replace Superclass with Delegate)
如果超類別的功能只適用於某些子類別,而有些子類別不是用,但子類別本身的特性又可以使用超類別的所有功能,這會造成一些問題,像是這種狀況就不應該藉由繼承來使用超類別的功能。