清單 9. 在對象之間鬆散耦合的好習慣
<?php
interface AddressFormatter { public function format($addressLine1, $addressLine2, $city, $state, $postalCode, $country); }
class MultiLineAddressFormatter implements AddressFormatter { public function format($addressLine1, $addressLine2, $city, $state, $postalCode, $country) { return sprintf("%sn%sn%s, %s %sn%s", $addressLine1, $addressLine2, $city, $state, $postalCode, $country); } }
class InlineAddressFormatter implements AddressFormatter { public function format($addressLine1, $addressLine2, $city, $state, $postalCode, $country) { return sprintf("%s %s, %s, %s %s %s", $addressLine1, $addressLine2, $city, $state, $postalCode, $country); } }
class AddressFormatUtils { public static function formatAddress($type, $address) { $formatter = AddressFormatUtils::createAddressFormatter($type); return $formatter->format($address->getAddressLine1(), $address->getAddressLine2(), $address->getCity(), $address->getState(), $address->getPostalCode(), $address->getCountry()); } private static function createAddressFormatter($type) { if ($type == "inline") { $formatter = new InlineAddressFormatter(); } else if ($type == "multiline") { $formatter = new MultilineAddressFormatter(); } else { $formatter = new NullAddressFormatter(); } return $formatter; } }
$addr = new Address(); $addr->setAddressLine1("123 Any St."); $addr->setAddressLine2("Ste 200"); $addr->setCity("Anytown"); $addr->setState("AY"); $addr->setPostalCode("55555-0000"); $addr->setCountry("US");
echo(AddressFormatUtils::formatAddress("multiline", $addr)); echo("n");
echo(AddressFormatUtils::formatAddress("inline", $addr)); echo("n"); ?>
|
當然,缺點是只要使用模式,通常就意味著工件(類、文件)的數量會增加。但是,通過減少每個類中的維護可以彌補這個缺點,甚至在獲得正確的可重用性時反而可以減少工件量。
您是橡皮;我是膠水
具有高度內聚力的 OO 設計被集中並組織到相關模塊中。瞭解 「關注點」 對於決定如何緊密地聯繫函數和類十分重要。
壞習慣:降低內聚力
當設計的內聚力較低 時,它就不能良好地組織類和方法。意大利麵條式代碼(spaghetti code)一詞通常用於描述捆綁在一起並且具有低內聚力的類和方法。清單 10 提供了意大利麵條式代碼的示例。相對通用的 Utils
類將使用許多不同對象並且有許多依賴關係。它執行很多操作,因而很難實現重用。
清單 10. 降低內聚力的壞習慣
<?php
class Utils { public static function formatAddress($formatType, $address1, $address2, $city, $state) { return "some address string"; } public static function formatPersonName($formatType, $givenName, $familyName) { return "some person name"; } public static function parseAddress($formatType, $val) { // real implementation would set values, etc... return new Address(); } public static function parseTelephoneNumber($formatType, $val) { // real implementation would set values, etc... return new TelephoneNumber(); } }
?>
|
好習慣:利用高內聚力
高內聚力 指將相互關聯的類和方法分組在一起。如果方法和類都具有高度的內聚力,則可以輕鬆地分解整個組而不影響設計。具有高內聚力的設計將提供降低耦合的機會。清單 11 顯示了被較好組織到類中的兩個方法。AddressUtils
類將包含用於處理 Address
類的方法,顯示了與地址相關的方法之間的高度內聚力。同樣地,PersonUtils
將包含專門處理 Person
對象的方法。這兩個擁有高度內聚力方法的新類的耦合性都很低,因為可以完全獨立地使用。
清單 11. 高內聚力的好習慣
<?php
class AddressUtils { public static function formatAddress($formatType, $address1, $address2, $city, $state) { return "some address string"; } public static function parseAddress($formatType, $val) { // real implementation would set values, etc... return new Address(); } }
class PersonUtils { public static function formatPersonName($formatType, $givenName, $familyName) { return "some person name"; } public static function parsePersonName($formatType, $val) { // real implementation would set values, etc... return new PersonName(); } }
?>
|
限制傳播
我經常對我所在的軟件團隊(我在其中擔任技術主管或架構師)的成員提起,OO 語言最大的敵人是複製和粘貼操作。當在缺少預先 OO
設計的情況下使用時,沒有任何操作會像在類之間複製代碼那樣具有破壞性。無論何時,如果想將代碼從一個類複製到下一個類中,請停下來並考慮如何使用類層次
結構利用類似功能或相同功能。在大多數情況下,使用優秀設計後,您將會發現完全沒有必要複製代碼。
壞習慣:不使用類層次結構
清單 12 顯示了部分類的簡單示例。它們從重複的字段和方法開始 — 從長遠來看,不利於應用程序作出更改。如果 Person
類中有缺陷,則 Employee
類中也很可能有一個缺陷,因為看上去似乎實現是在兩個類之間複製的。
清單 12. 不使用層次結構的壞習慣
<?php class Person { private $givenName; private $familyName; }
class Employee { private $givenName; private $familyName; }
?>
|
繼承 是一個很難入手的習慣,因為構建正確繼承模型的分析通常需要花費大量時間。反過來,使用 Ctrl+C 組合鍵和 Ctrl+V 組合鍵構建新實現只需幾秒鐘。但是省下的這部分時間通常會在維護階段迅速抵銷掉,因為應用程序實際上將花費大量進行維護。
好習慣:利用繼承
在清單 13 中,新 Employee
類將擴展 Person
類。它現在將繼承所有通用方法並且不重新實現這些方法。此外,清單 13 顯示了抽象方法的用法,演示如何將基本功能放入基類中以及如何阻止實現類使用特定函數。
清單 13. 利用繼承的好習慣
<?php abstract class Person { private $givenName; private $familyName; public function setGivenName($gn) { $this->givenName = $gn; } public function getGivenName() { return $this->givenName; } public function setFamilyName($fn) { $this->familyName = $fn; } public function getFamilyName() { return $this->familyName; } public function sayHello() { echo("Hello, I am "); $this->introduceSelf(); } abstract public function introduceSelf(); }
class Employee extends Person { private $role; public function setRole($r) { $this->role = $r; } public function getRole() { return $this->role; } public function introduceSelf() { echo($this->getRole() . " " . $this->getGivenName() . " " . $this->getFamilyName()); } } ?>
|
考慮使用模式
設計模式指對象和方法的常見交互,並且時間證明它可以解決某些問題。當您考慮使用設計模式時,您就需要瞭解類之間如何進行交互。它是構建類及其交互操作的簡單方法,無需重蹈他人的覆轍,並從經過證明的設計中獲益。
壞習慣:一次考慮一個對象
實際上沒有適當的代碼示例可以演示如何考慮使用模式(儘管有豐富的優秀示例可以顯示模式實現)。但是,一般而言,您知道在滿足以下條件時一次只能考慮一個對象:
- 不會提前設計對象模型。
- 開始編寫單一方法的實現,而無需去掉大部分模型。
- 在交談中不使用設計模式名而寧願談論實現。
好習慣:同時添加模式中形成的對象
一般而言,當您在執行以下操作時就是在考慮使用模式:
- 提前構建類及其交互操作。
- 根據模式套用類。
- 使用模式名,如 Factory、Singleton 和 Facade。
- 去掉大部分模型,然後開始添加實現。
結束語
在 PHP 中養成良好的 OO 習慣將幫助您構建更穩定、更易於維護和更易於擴展的應用程序。記住:
- 保持謹慎。
- 做個好鄰居。
- 避免看到美杜莎。
- 利用最弱的鏈接。
- 您是橡皮,我是膠水。
- 限制傳播。
- 考慮使用模式。
當您養成並應用這些習慣後,您很可能會驚訝地發現應用程序在質量上的飛躍。
參考資料
學習