|
如果您尚未打算用 OO 原則創(chuàng)建應(yīng)用程序,則使用 php 的面向?qū)ο螅∣O)的語言特性,這 7 個(gè)習(xí)慣將幫助您開始在過程編程與 OO 編程之間進(jìn)行轉(zhuǎn)換。
在 php 編程早期,php 代碼在本質(zhì)上是限于面向過程的。過程代碼 的特征在于使用過程構(gòu)建應(yīng)用程序塊。過程通過允許過程之間的調(diào)用提供某種程度的重用。
但是,沒有面向?qū)ο蟮恼Z言構(gòu)造,程序員仍然可以把 OO 特性引入到 php 代碼中。這樣做有點(diǎn)困難并且會(huì)使代碼難于閱讀,因?yàn)樗腔旌戏独ê袀?OO 設(shè)計(jì)的過程語言)。使用 php 代碼中的 OO 構(gòu)造 ― 例如能夠定義和使用類、能夠構(gòu)建使用繼承的類之間的關(guān)系以及能夠定義接口 ― 可以更輕松地構(gòu)建符合優(yōu)秀 OO 實(shí)踐的代碼。
雖然沒有過多模塊化的純過程設(shè)計(jì)運(yùn)行得很好,但是 OO 設(shè)計(jì)的優(yōu)點(diǎn)表現(xiàn)在維護(hù)上。由于典型應(yīng)用程序的大部分生命周期都花費(fèi)在維護(hù)上,因此代碼維護(hù)是應(yīng)用程序生命周期的重要部分。并且在開發(fā)過程中代碼維護(hù)很容易被遺忘。如果在應(yīng)用程序開發(fā)和部署方面存在競(jìng)爭(zhēng),那么長(zhǎng)期可維護(hù)性可能被放在比較次要的地位。
模塊化 ― 優(yōu)秀 OO 設(shè)計(jì)的主要特性之一 ― 可以幫助完成這樣的維護(hù)。模塊化將幫助封裝更改,這樣可以隨著時(shí)間的推移更輕松地?cái)U(kuò)展和修改應(yīng)用程序。
總的來說,雖然構(gòu)建 OO 軟件的習(xí)慣不止 7 個(gè),但是遵循這里的 7 個(gè)習(xí)慣可以使代碼符合基本 OO 設(shè)計(jì)標(biāo)準(zhǔn)。它們將為您提供更牢固的基礎(chǔ),在此基礎(chǔ)之上建立更多 OO 習(xí)慣并構(gòu)建可輕松維護(hù)與擴(kuò)展的軟件。這些習(xí)慣針對(duì)模塊化的幾個(gè)主要特性。有關(guān)獨(dú)立于語言的 OO 設(shè)計(jì)優(yōu)點(diǎn)的更多信息,請(qǐng)參閱 參考資料。
7 個(gè)優(yōu)秀 php OO 習(xí)慣包括:
保持謙虛。
做個(gè)好鄰居。
避免看到美杜莎。
利用最弱的鏈接。
您是橡皮;我是膠水。
限制傳播。
考慮使用模式。
保持謙虛
保持謙虛指避免在類實(shí)現(xiàn)和函數(shù)實(shí)現(xiàn)中暴露自己。隱藏您的信息是一項(xiàng)基本習(xí)慣。如果不能養(yǎng)成隱藏實(shí)現(xiàn)細(xì)節(jié)的習(xí)慣,那么將很難養(yǎng)成任何其他習(xí)慣。信息隱藏也稱為封裝。
直接公開公共字段是一個(gè)壞習(xí)慣的原因有很多,最重要的原因是讓您在實(shí)現(xiàn)更改中沒有應(yīng)有的選擇。使用 OO 概念隔離更改,而封裝在確保所作更改在本質(zhì)上不是病毒性(viral)更改方面扮演不可或缺的角色。病毒性 更改是開始時(shí)很小的更改 ― 如將保存三個(gè)元素的數(shù)組更改為一個(gè)只包含兩個(gè)元素的數(shù)組。突然,您發(fā)現(xiàn)需要更改越來越多的代碼以適應(yīng)本應(yīng)十分微不足道的更改。
開始隱藏信息的一種簡(jiǎn)單方法是保持字段私有并且用公共訪問方法公開這些字段,就像家中的窗戶一樣。并沒有讓整面墻都朝外部開放,而只打開一兩扇窗戶(我將在 “好習(xí)慣:使用公共訪問方法” 中介紹訪問方法的更多信息)。
除了允許您的實(shí)現(xiàn)隱藏在更改之后外,使用公共訪問方法而非直接公開字段將允許您在基本實(shí)現(xiàn)的基礎(chǔ)上進(jìn)行構(gòu)建,方法為覆蓋訪問方法的實(shí)現(xiàn)以執(zhí)行略微不同于父方法的行為。它還允許您構(gòu)建一個(gè)抽象實(shí)現(xiàn),從而使實(shí)際實(shí)現(xiàn)委托給覆蓋基本實(shí)現(xiàn)的類。
壞習(xí)慣:公開公共字段
在清單 1 的壞代碼示例中,Person 對(duì)象的字段被直接公開為公共字段而非使用訪問方法。雖然此行為十分誘人,尤其對(duì)于輕量級(jí)數(shù)據(jù)對(duì)象來說更是如此,但是它將對(duì)您提出限制。
清單 1. 公開公共字段的壞習(xí)慣
復(fù)制代碼 代碼如下:
<?php
class Person
{
public $prefix;
public $givenName;
public $familyName;
public $suffix;
}
$person = new Person();
$person->prefix = "Mr.";
$person->givenName = "John";
echo($person->prefix);
echo($person->givenName);
?>
如果對(duì)象有任何更改,則使用該對(duì)象的所有代碼也都需要更改。例如,如果某人的教名、姓氏和其他名字被封裝到 PersonName 對(duì)象中,則需要修改所有代碼以適應(yīng)更改。
好習(xí)慣:使用公共訪問方法
通過使用優(yōu)秀的 OO 習(xí)慣(參見清單 2),同一個(gè)對(duì)象現(xiàn)在擁有私有字段而非公共字段,并且通過稱為訪問方法 的 get 和 set 公共方法謹(jǐn)慎地向外界公開私有字段。這些訪問方法現(xiàn)在提供了一種從 php 類中獲取信息的公共方法,這樣在實(shí)現(xiàn)發(fā)生更改時(shí),更改使用類的所有代碼的需求很可能變小。
清單 2. 使用公共訪問方法的好習(xí)慣
復(fù)制代碼 代碼如下:
<?php
class Person
{
private $prefix;
private $givenName;
private $familyName;
private $suffix;
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
public function getPrefix()
{
return $this->prefix;
}
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 setSuffix($suffix)
{
$this->suffix = $suffix;
}
public function getSuffix()
{
return $suffix;
}
}
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?>
乍看之下,這段代碼可能會(huì)完成大量工作,并且實(shí)際上可能更多是在前端的工作。但是,通常,使用優(yōu)秀的 OO 習(xí)慣從長(zhǎng)遠(yuǎn)來看十分劃算,因?yàn)閷O大地鞏固未來更改。
在清單 3 中所示的代碼版本中,我已經(jīng)更改了內(nèi)部實(shí)現(xiàn)以使用名稱部件的關(guān)聯(lián)數(shù)組。比較理想的情況是,我希望擁有錯(cuò)誤處理并且更仔細(xì)地檢查元素是否存在,但是本例的目的在于展示使用我的類的代碼無需更改的程度 ― 代碼并沒有察覺到類發(fā)生更改。記住采用 OO 習(xí)慣的原因是要謹(jǐn)慎封裝更改,這樣代碼將更具有可擴(kuò)展性并且更容易維護(hù)。
清單 3. 使用不同內(nèi)部實(shí)現(xiàn)的另一個(gè)示例
復(fù)制代碼 代碼如下:
<?php
class Person
{
private $personName = array();
public function setPrefix($prefix)
{
$this->personName['prefix'] = $prefix;
}
public function getPrefix()
{
return $this->personName['prefix'];
}
public function setGivenName($gn)
{
$this->personName['givenName'] = $gn;
}
public function getGivenName()
{
return $this->personName['givenName'];
}
/* etc... */
}
/*
* Even though the internal implementation changed, the code here stays exactly
* the same. The change has been encapsulated only to the Person class.
*/
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?>
做個(gè)好鄰居
在構(gòu)建類時(shí),它應(yīng)當(dāng)正確地處理自己的錯(cuò)誤。如果該類不知道如何處理錯(cuò)誤,則應(yīng)當(dāng)以其調(diào)用者理解的格式封裝這些錯(cuò)誤。此外,避免返回空對(duì)象或者狀態(tài)無效的對(duì)象。許多時(shí)候,只需通過檢驗(yàn)參數(shù)并拋出特定異常說明提供參數(shù)無效的原因就可以實(shí)現(xiàn)這一點(diǎn)。在您養(yǎng)成這個(gè)習(xí)慣時(shí),它可以幫您 ― 和維護(hù)代碼或使用對(duì)象的人員 ― 節(jié)省很多時(shí)間。
壞習(xí)慣:不處理錯(cuò)誤
考慮清單 4 中所示的示例,該示例將接受一些參數(shù)并返回填充了一些值的 Person 對(duì)象。但是,在 parsePersonName() 方法中,沒有驗(yàn)證提供的 $val 變量是否為空、是否是零長(zhǎng)度字符串或者字符串是否使用無法解析的格式。parsePersonName() 方法不返回 Person 對(duì)象,但是返回 null。使用這種方法的管理員或程序員可能會(huì)覺得很麻煩 ― 至少他們現(xiàn)在需要開始設(shè)置斷點(diǎn)并調(diào)試 php 腳本。
清單 4. 不拋出或處理錯(cuò)誤的壞習(xí)慣
復(fù)制代碼 代碼如下:
class PersonUtils
{
public static function parsePersonName($format, $val)
{
if (strpos(",", $val) > 0) {
$person = new Person();
$parts = split(",", $val); // Assume the value is last, first
$person->setGivenName($parts[1]);
$person->setFamilyName($parts[0]);
}
return $person;
}
}
清單 4 中的 parsePersonName() 方法可以修改為在 if 條件外部初始化 Person 對(duì)象,確保總是獲得有效的 Person 對(duì)象。但是,您得到的是沒有 set 屬性的 Person,這仍然沒有很好地改善您的困境。
好習(xí)慣:每個(gè)模塊都處理自己的錯(cuò)誤
不要讓調(diào)用方憑空猜測(cè),而是對(duì)參數(shù)進(jìn)行預(yù)先驗(yàn)證。如果未設(shè)置的變量無法生成有效的結(jié)果,請(qǐng)檢查變量并拋出 InvalidArgumentException。如果字符串不能為空或者必須為特定格式,請(qǐng)檢查格式并拋出異常。清單 5 解釋了如何在演示一些基本驗(yàn)證的 parsePerson() 方法中創(chuàng)建異常以及一些新條件。
清單 5. 拋出錯(cuò)誤的好習(xí)慣
復(fù)制代碼 代碼如下:
<?php
class InvalidPersonNameFormatException extends LogicException {}
class PersonUtils
{
public static function parsePersonName($format, $val)
{
if (! $format) {
throw new InvalidPersonNameFormatException("Invalid PersonName format.");
}
if ((! isset($val)) || strlen($val) == 0) {
throw new InvalidArgumentException("Must supply a non-null value to parse.");
}
}
}
?>
最終目的是希望人們能夠使用您的類,而不必了解其中的工作原理。如果他們使用的方法不正確或者不是按照期望的方法使用,也不需要猜測(cè)不能工作的原因。作為一個(gè)好鄰居,您需要知道對(duì)您的類進(jìn)行重用的人并沒有特異功能,因此您需要解決猜測(cè)的問題。
避免看到美杜莎
在我最初了解 OO 概念時(shí),我十分懷疑接口是否真正有幫助。我的同事給我打了個(gè)比方,說不使用接口就好像看到美杜莎的頭。在希臘神話中,美杜莎是長(zhǎng)著蛇發(fā)的女怪。凡是看了她一眼的人都會(huì)變成石頭。殺死美杜莎的珀?duì)栃菟雇ㄟ^在盾上觀察她的影子,避免了變成石頭而得以與她對(duì)抗。
接口就是對(duì)付美杜莎的鏡子。當(dāng)您使用一個(gè)特定的具體實(shí)現(xiàn)時(shí),代碼也必須隨著實(shí)現(xiàn)代碼的更改而更改。直接使用實(shí)現(xiàn)將限制您的選擇,因?yàn)槟呀?jīng)在本質(zhì)上把類變成了 “石頭”。
壞習(xí)慣:不使用接口
清單 6 顯示了從數(shù)據(jù)庫中裝入 Person 對(duì)象的示例。它將獲取人員的姓名并返回?cái)?shù)據(jù)庫中匹配的 Person 對(duì)象。
清單 6. 不使用接口的壞習(xí)慣
復(fù)制代碼 代碼如下:
<?php
class DBPersonProvider
{
public function getPerson($givenName, $familyName)
{
/* go to the database, get the person... */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return $person;
}
}
/* I need to get person data... */
$provider = new DBPersonProvider();
$person = $provider->getPerson("John", "Doe");
echo($person->getPrefix());
echo($person->getGivenName());
?>
在環(huán)境發(fā)生更改之前,從數(shù)據(jù)庫中裝入 Person 的代碼都可以正常運(yùn)行。例如,從數(shù)據(jù)庫裝入 Person 可能適用于第一個(gè)版本的應(yīng)用程序,但是對(duì)于第二個(gè)版本,可能需要添加從 Web 服務(wù)裝入人員的功能。其實(shí),該類已經(jīng)變成 “石頭”,因?yàn)樗谥苯邮褂脤?shí)現(xiàn)類并且現(xiàn)在能做的更改十分有限。
好習(xí)慣:使用接口
清單 7 顯示了一個(gè)代碼示例,在實(shí)現(xiàn)了加載用戶的新方法后并沒有進(jìn)行更改。該示例顯示了一個(gè)名為 PersonProvider 的接口,該接口將聲明單個(gè)方法。如果任何代碼使用 PersonProvider,代碼都禁止直接使用實(shí)現(xiàn)類。相反,它就像是一個(gè)實(shí)際對(duì)象一樣使用 PersonProvider。
清單 7. 使用接口的好習(xí)慣
復(fù)制代碼 代碼如下:
<?php
interface PersonProvider
{
public function getPerson($givenName, $familyName);
}
class DBPersonProvider implements PersonProvider
{
public function getPerson($givenName, $familyName)
{
/* pretend to go to the database, get the person... */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
return $person;
}
}
class PersonProviderFactory
{
public static function createProvider($type)
{
if ($type == 'database')
{
return new DBPersonProvider();
} else {
return new NullProvider();
}
}
}
$config = 'database';
/* I need to get person data... */
$provider = PersonProviderFactory::createProvider($config);
$person = $provider->getPerson("John", "Doe");
echo($person->getPrefix());
echo($person->getGivenName());
?>
在使用接口時(shí),嘗試避免直接引用實(shí)現(xiàn)類。相反,使用對(duì)象外部的內(nèi)容可以提供正確的實(shí)現(xiàn)。如果您的類將裝入基于某些邏輯的實(shí)現(xiàn),它仍然需要獲取所有實(shí)現(xiàn)類的定義,并且那樣做也無法取得任何效果。
您可以使用 Factory 模式來創(chuàng)建實(shí)現(xiàn)接口的實(shí)現(xiàn)類的實(shí)例。根據(jù)約定,factory 方法將以 create 為開頭并返回接口。它可以為您的 factory 獲取必要的參數(shù)以計(jì)算出應(yīng)當(dāng)返回哪個(gè)實(shí)現(xiàn)類。
在清單 7 中,createProvider() 方法只是獲取 $type。如果 $type 被設(shè)為 database,工廠將返回 DBPersonProvider 的實(shí)例。從數(shù)據(jù)庫中裝入人員的任何新實(shí)現(xiàn)都不要求在使用工廠和接口的類中進(jìn)行任何更改。DBPersonProvider 將實(shí)現(xiàn) PersonProvider 接口并且擁有 getPerson() 方法的實(shí)際實(shí)現(xiàn)。
利用最弱的鏈接
將模塊松散耦合 在一起是件好事情;它是允許您封裝更改的屬性之一。另外兩個(gè)習(xí)慣 ― “保持謹(jǐn)慎” 和 “避免看到美杜莎” ― 可幫助您構(gòu)建松散耦合的模塊。要實(shí)現(xiàn)松散耦合的類,可通過養(yǎng)成降低類依賴關(guān)系的習(xí)慣實(shí)現(xiàn)。
壞習(xí)慣:緊密耦合
在清單 8 中,降低依賴關(guān)系并不是必須降低使用對(duì)象的客戶機(jī)的依賴關(guān)系。相反,該示例將演示如何降低與正確類的依賴關(guān)系并最小化這種依賴關(guān)系。
清單 8. Address 中緊密耦合的壞習(xí)慣
復(fù)制代碼 代碼如下:
<?php
require_once "./AddressFormatters.php";
class Address
{
private $addressLine1;
private $addressLine2;
private $city;
private $state; // or province...
private $postalCode;
private $country;
public function setAddressLine1($line1)
{
$this->addressLine1 = $line1;
}
/* accessors, etc... */
public function getCountry()
{
return $this->country;
}
public function format($type)
{
if ($type == "inline") {
$formatter = new InlineAddressFormatter();
} else if ($type == "multiline") {
$formatter = new MultilineAddressFormatter();
} else {
$formatter = new NullAddressFormatter();
}
return $formatter->format($this->getAddressLine1(),
$this->getAddressLine2(),
$this->getCity(), $this->getState(), $this->getPostalCode(),
$this->getCountry());
}
}
$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($addr->format("multiline"));
echo("/n");
echo($addr->format("inline"));
echo("/n");
?>
在 Address 對(duì)象上調(diào)用 format() 方法的代碼可能看上去很棒 ― 這段代碼所做的是使用 Address 類,調(diào)用 format() 并完成。相反,Address 類就沒那么幸運(yùn)。它需要了解用于正確格式化的各種格式化方法,這可能使 Address 對(duì)象無法被其他人很好地重用,尤其是在其他人沒有興趣在 format() 方法中使用格式化方法類的情況下。雖然使用 Address 的代碼沒有許多依賴關(guān)系,但是 Address 類卻有大量代碼,而它可能只是一個(gè)簡(jiǎn)單的數(shù)據(jù)對(duì)象。
Address 類與知道如何格式化 Address 對(duì)象的實(shí)現(xiàn)類緊密耦合。
好習(xí)慣:在對(duì)象之間松散耦合
在構(gòu)建優(yōu)秀的 OO 設(shè)計(jì)時(shí),必須考慮稱為關(guān)注點(diǎn)分離(Separation of Concerns,SoC)的概念。SoC 指嘗試通過真正關(guān)注的內(nèi)容分離對(duì)象,從而降低耦合度。在最初的 Address 類中,它必須關(guān)注如何進(jìn)行格式化。這可能不是優(yōu)秀的設(shè)計(jì)。然而,Address 類應(yīng)當(dāng)考慮 Address 的各部分,而某種格式化方法應(yīng)當(dāng)關(guān)注如何正確格式化地址。
在清單 9 中,格式化地址的代碼被移到接口、實(shí)現(xiàn)類和工廠中 ― 養(yǎng)成 “使用接口” 的習(xí)慣。現(xiàn)在,AddressFormatUtils 類負(fù)責(zé)創(chuàng)建格式化方法并格式化 Address。任何其他對(duì)象現(xiàn)在都可以使用 Address 而不必?fù)?dān)心要求獲得格式化方法的定義。
清單 9. 在對(duì)象之間松散耦合的好習(xí)慣
復(fù)制代碼 代碼如下:
<?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("%s/n%s/n%s, %s %s/n%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");
?>
當(dāng)然,缺點(diǎn)是只要使用模式,通常就意味著工件(類、文件)的數(shù)量會(huì)增加。但是,通過減少每個(gè)類中的維護(hù)可以彌補(bǔ)這個(gè)缺點(diǎn),甚至在獲得正確的可重用性時(shí)反而可以減少工件量。
您是橡皮;我是膠水
具有高度內(nèi)聚力的 OO 設(shè)計(jì)被集中并組織到相關(guān)模塊中。了解 “關(guān)注點(diǎn)” 對(duì)于決定如何緊密地聯(lián)系函數(shù)和類十分重要。
壞習(xí)慣:降低內(nèi)聚力
當(dāng)設(shè)計(jì)的內(nèi)聚力較低 時(shí),它就不能良好地組織類和方法。意大利面條式代碼(spaghetti code)一詞通常用于描述捆綁在一起并且具有低內(nèi)聚力的類和方法。清單 10 提供了意大利面條式代碼的示例。相對(duì)通用的 Utils 類將使用許多不同對(duì)象并且有許多依賴關(guān)系。它執(zhí)行很多操作,因而很難實(shí)現(xiàn)重用。
清單 10. 降低內(nèi)聚力的壞習(xí)慣
復(fù)制代碼 代碼如下:
<?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();
}
}
?>
好習(xí)慣:利用高內(nèi)聚力
高內(nèi)聚力 指將相互關(guān)聯(lián)的類和方法分組在一起。如果方法和類都具有高度的內(nèi)聚力,則可以輕松地分解整個(gè)組而不影響設(shè)計(jì)。具有高內(nèi)聚力的設(shè)計(jì)將提供降低耦合的機(jī)會(huì)。清單 11 顯示了被較好組織到類中的兩個(gè)方法。AddressUtils 類將包含用于處理 Address 類的方法,顯示了與地址相關(guān)的方法之間的高度內(nèi)聚力。同樣地,PersonUtils 將包含專門處理 Person 對(duì)象的方法。這兩個(gè)擁有高度內(nèi)聚力方法的新類的耦合性都很低,因?yàn)榭梢酝耆?dú)立地使用。
清單 11. 高內(nèi)聚力的好習(xí)慣
復(fù)制代碼 代碼如下:
<?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();
}
}
?>
限制傳播
我經(jīng)常對(duì)我所在的軟件團(tuán)隊(duì)(我在其中擔(dān)任技術(shù)主管或架構(gòu)師)的成員提起,OO 語言最大的敵人是復(fù)制和粘貼操作。當(dāng)在缺少預(yù)先 OO 設(shè)計(jì)的情況下使用時(shí),沒有任何操作會(huì)像在類之間復(fù)制代碼那樣具有破壞性。無論何時(shí),如果想將代碼從一個(gè)類復(fù)制到下一個(gè)類中,請(qǐng)停下來并考慮如何使用類層次結(jié)構(gòu)利用類似功能或相同功能。在大多數(shù)情況下,使用優(yōu)秀設(shè)計(jì)后,您將會(huì)發(fā)現(xiàn)完全沒有必要復(fù)制代碼。
壞習(xí)慣:不使用類層次結(jié)構(gòu)
清單 12 顯示了部分類的簡(jiǎn)單示例。它們從重復(fù)的字段和方法開始 ― 從長(zhǎng)遠(yuǎn)來看,不利于應(yīng)用程序作出更改。如果 Person 類中有缺陷,則 Employee 類中也很可能有一個(gè)缺陷,因?yàn)榭瓷先ニ坪鯇?shí)現(xiàn)是在兩個(gè)類之間復(fù)制的。
清單 12. 不使用層次結(jié)構(gòu)的壞習(xí)慣
復(fù)制代碼 代碼如下:
<?php
class Person
{
private $givenName;
private $familyName;
}
class Employee
{
private $givenName;
private $familyName;
}
?>
繼承 是一個(gè)很難入手的習(xí)慣,因?yàn)闃?gòu)建正確繼承模型的分析通常需要花費(fèi)大量時(shí)間。反過來,使用 Ctrl+C 組合鍵和 Ctrl+V 組合鍵構(gòu)建新實(shí)現(xiàn)只需幾秒鐘。但是省下的這部分時(shí)間通常會(huì)在維護(hù)階段迅速抵銷掉,因?yàn)?a href=/pingce/yingyong/ target=_blank class=infotextkey>應(yīng)用程序?qū)嶋H上將花費(fèi)大量進(jìn)行維護(hù)。
好習(xí)慣:利用繼承
在清單 13 中,新 Employee 類將擴(kuò)展 Person 類。它現(xiàn)在將繼承所有通用方法并且不重新實(shí)現(xiàn)這些方法。此外,清單 13 顯示了抽象方法的用法,演示如何將基本功能放入基類中以及如何阻止實(shí)現(xiàn)類使用特定函數(shù)。
清單 13. 利用繼承的好習(xí)慣
復(fù)制代碼 代碼如下:
<?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());
}
}
?>
考慮使用模式
設(shè)計(jì)模式指對(duì)象和方法的常見交互,并且時(shí)間證明它可以解決某些問題。當(dāng)您考慮使用設(shè)計(jì)模式時(shí),您就需要了解類之間如何進(jìn)行交互。它是構(gòu)建類及其交互操作的簡(jiǎn)單方法,無需重蹈他人的覆轍,并從經(jīng)過證明的設(shè)計(jì)中獲益。
壞習(xí)慣:一次考慮一個(gè)對(duì)象
實(shí)際上沒有適當(dāng)?shù)拇a示例可以演示如何考慮使用模式(盡管有豐富的優(yōu)秀示例可以顯示模式實(shí)現(xiàn))。但是,一般而言,您知道在滿足以下條件時(shí)一次只能考慮一個(gè)對(duì)象:
不會(huì)提前設(shè)計(jì)對(duì)象模型。
開始編寫單一方法的實(shí)現(xiàn),而無需去掉大部分模型。
在交談中不使用設(shè)計(jì)模式名而寧愿談?wù)搶?shí)現(xiàn)。
好習(xí)慣:同時(shí)添加模式中形成的對(duì)象
一般而言,當(dāng)您在執(zhí)行以下操作時(shí)就是在考慮使用模式:
提前構(gòu)建類及其交互操作。
根據(jù)模式套用類。
使用模式名,如 Factory、Singleton 和 Facade。
去掉大部分模型,然后開始添加實(shí)現(xiàn)。
結(jié)束語
在 php 中養(yǎng)成良好的 OO 習(xí)慣將幫助您構(gòu)建更穩(wěn)定、更易于維護(hù)和更易于擴(kuò)展的應(yīng)用程序。記住:
保持謹(jǐn)慎。
做個(gè)好鄰居。
避免看到美杜莎。
利用最弱的鏈接。
您是橡皮,我是膠水。
限制傳播。
考慮使用模式。
當(dāng)您養(yǎng)成并應(yīng)用這些習(xí)慣后,您很可能會(huì)驚訝地發(fā)現(xiàn)應(yīng)用程序在質(zhì)量上的飛躍。
php技術(shù):在PHP中養(yǎng)成7個(gè)面向?qū)ο蟮暮昧?xí)慣,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。