這差不多是一個關(guān)于數(shù)組設(shè)計的風(fēng)格指南,但是把它添加到對象設(shè)計風(fēng)格指南感覺不太對,因為不是所有的面向?qū)ο笳Z言都有動態(tài)數(shù)組。本文中的示例是用 PHP 編寫的,因為 PHP 很像 Java(可能比較熟悉),但是使用的是動態(tài)數(shù)組而不是內(nèi)置的集合類和接口。
創(chuàng)新互聯(lián)專注于訥河企業(yè)網(wǎng)站建設(shè),自適應(yīng)網(wǎng)站建設(shè),商城網(wǎng)站建設(shè)。訥河網(wǎng)站建設(shè)公司,為訥河等地區(qū)提供建站服務(wù)。全流程按需設(shè)計網(wǎng)站,專業(yè)設(shè)計,全程項目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
當(dāng)使用一個數(shù)組作為一個列表(一個具有特定順序的值的集合)時,每個值應(yīng)該是 z 類型:
$goodList = [ 'a', 'b' ]; $badList = [ 'a', 1 ];
一個被普遍接受的注釋列表類型的風(fēng)格是:@var array
。 確保不添加索引的類型(它總是int
)。
PHP 將自動為列表中的每個元素(0、1、2等)創(chuàng)建新索引。然而,你不應(yīng)該依賴這些索引,也不應(yīng)該直接使用它們。客戶端應(yīng)該依賴的列表的唯一屬性是可迭代的和可計數(shù)的。
因此,可以隨意使用foreach
和count()
,但不要使用for
循環(huán)遍歷列表中的元素:
// 好的循環(huán): foreach ($list as $element) { } // 不好的循環(huán) (公開每個元素的索引): foreach ($list as $index => $element) { } // 也是不好的循環(huán) (不應(yīng)該使用每個元素的索引): for ($i = 0; $i < count($list); $i++) { }
(在 PHP 中,for
循環(huán)甚至可能不起作用,因為列表中可能缺少索引,而且索引可能比列表中的元素數(shù)量還要多。)
你可能希望通過索引從列表中刪除元素(unset()
),但是,你應(yīng)該使用array_filter()
來創(chuàng)建一個新列表(沒有不需要的元素),而不是刪除元素。
同樣,你不應(yīng)該依賴于元素的索引,因此,在使用array_filter()
時,不應(yīng)該使用flag
參數(shù)去根據(jù)索引過濾元素,甚至根據(jù)元素和索引過濾元素。
// 好的過濾: array_filter( $list, function (string $element): bool { return strlen($element) > 2; } ); // 不好的過濾器(也使用索引來過濾元素) array_filter( $list, function (int $index): bool { return $index > 3; }, ARRAY_FILTER_USE_KEY ); // 不好的過濾器(同時使用索引和元素來過濾元素) array_filter( $list, function (string $element, int $index): bool { return $index > 3 || $element === 'Include'; }, ARRAY_FILTER_USE_BOTH );
當(dāng)鍵是相關(guān)的,而不是索引(0,1,2,等等)。你可以隨意使用數(shù)組作為映射(可以通過其唯一的鍵從其中檢索值)。
使用數(shù)組作為映射的第一個規(guī)則是,數(shù)組中的所有鍵都應(yīng)該具有相同的類型(最常見的是string
類型的鍵)。
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ]; // 不好(使用不同類型的鍵) $badMap = [ 'foo' => 'bar', 1 => 'baz' ];
映射中的值也是如此:它們應(yīng)該具有相同的類型。
$goodMap = [ 'foo' => 'bar', 'bar' => 'baz' ]; // 不好(使用不同類型的值) $badMap = [ 'foo' => 'bar', 'bar' => 1 ];
一種普遍接受的映射類型注釋樣式是: @var array
。
列表可以安全地在對象之間傳遞,因為它們具有簡單的特征。任何客戶端都可以使用它來循環(huán)其元素,或計數(shù)其元素,即使列表是空的。映射則更難處理,因為客戶端可能依賴于沒有對應(yīng)值的鍵。這意味著在一般情況下,它們應(yīng)該對管理它們的對象保持私有。不允許客戶端直接訪問內(nèi)部映射,而是提供 getter (可能還有 setter )來檢索值。如果請求的鍵不存在值,則拋出異常。但是,如果您可以保持映射及其值完全私有,那么就這樣做。
// 公開一個列表是可以的 /** * @return array*/ public function allUsers(): array { // ... } // 公開地圖可能很麻煩 /** * @return array */ public function usersById(): array { // ... } // 相反,提供一種方法來根據(jù)其鍵檢索值 /** * @throws UserNotFound */ public function userById(string $id): User { // ... }
當(dāng)你想要在一個映射中存儲不同類型的值時,請使用一個對象。定義一個類,并向其添加公共的類型化屬性,或添加構(gòu)造函數(shù)和 getter。像這樣的對象的例子是配置對象,或者命令對象:
final class SillyRegisterUserCommand { public string $username; public string $plainTextPassword; public bool $wantsToReceiveSpam; public int $answerToIAmNotARobotQuestion; }
有時,庫或框架需要以更動態(tài)的方式使用數(shù)組。在這些情況下,不可能(也不希望)遵循前面的規(guī)則。例如數(shù)組數(shù)據(jù),它將被存儲在一個數(shù)據(jù)庫表中,或者Symfony 表單配置。
自定義集合類是一種非??岬姆椒?,最后可以和Iterator
、ArrayAccess
和其朋友一起使用,但是我發(fā)現(xiàn)大多數(shù)生成的代碼令人很困惑。第一次查看代碼的人必須在 PHP 手冊中查找詳細信息,即使他們是有經(jīng)驗的開發(fā)人員。另外,你需要編寫更多的代碼,你必須維護這些代碼(測試、調(diào)試等)。所以在大多數(shù)情況下,我發(fā)現(xiàn)一個簡單的數(shù)組,加上一些適當(dāng)?shù)念愋妥⑨專妥銐蛄?。到底什么是需要將?shù)組封裝到自定義集合對象中的強信號?
如果使用相同數(shù)組的多個客戶端執(zhí)行相同的任務(wù)(例如過濾、映射、減少、計數(shù)),則可以通過引入自定義集合類來消除重復(fù)。將重復(fù)的邏輯移到集合類的一個方法上,允許任何客戶端使用對集合的簡單方法調(diào)用來執(zhí)行相同的任務(wù):
$names = [/* ... */]; // 在幾個地方發(fā)現(xiàn): $shortNames = array_filter( $names, function (string $element): bool { return strlen($element) < 5; } ); // 變成一個自定義集合類: use Assert\Assert; final class Names { /** * @var array*/ private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function shortNames(): self { return new self( array_filter( $this->names, function (string $element): bool { return strlen($element) < 5; } ) ); } } $names = new Names([/* ... */]); $shortNames = $names->shortNames();
在集合的轉(zhuǎn)換上使用方法的好處就是獲得了一個名稱。這使你能夠向看起來相當(dāng)復(fù)雜的array_filter()
調(diào)用添加一個簡短而有意義的標(biāo)簽。
如果一個客戶端使用特定的數(shù)組并循環(huán),從選定的元素中取出一段數(shù)據(jù),并對該數(shù)據(jù)進行處理,那么該客戶端就與所有涉及的類型緊密耦合: 數(shù)組本身、數(shù)組中元素的類型、它從所選元素中檢索的值的類型、選擇器方法的類型,等等。這種深度耦合的問題是,在不破壞依賴于它們的客戶端的情況下,很難更改所涉及類型的任何內(nèi)容。因此,在這種情況下,你也可以將數(shù)組包裝在一個自定義 的集合類中,讓它一次性給出正確的答案,在內(nèi)部進行必要的計算,讓客戶端與集合更加松散地耦合。
$lines = []; $sum = 0; foreach ($lines as $line) { if ($line->isComment()) { continue; } $sum += $line->quantity(); } // Turned into a custom collection class: final class Lines { public function totalQuantity(): int { $sum = 0; foreach ($lines as $line) { if ($line->isComment()) { continue; } $sum += $line->quantity(); } return $sum; } }
讓我們看看在使用自定義集合類時應(yīng)用的一些規(guī)則。
對集合實例的現(xiàn)有引用在運行某種轉(zhuǎn)換時不應(yīng)受到影響。因此,任何執(zhí)行轉(zhuǎn)換的方法都應(yīng)該返回類的一個新實例,就像我們在上面的例子中看到的那樣:
final class Names { /** * @var array*/ private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function shortNames(): self { return new self( /* ... */ ); } }
當(dāng)然,如果要映射內(nèi)部數(shù)組,則可能要映射到另一種類型的集合或簡單數(shù)組。與往常一樣,請確保提供適當(dāng)?shù)姆祷仡愋汀?/p>
你不必擴展泛型集合庫類,也不必自己在每個自定義集合類上實現(xiàn)泛型篩選器、映射和縮減方法,只實現(xiàn)真正需要的。如果某個方法在某一時刻不被使用,那么就刪除它。
如果你使用 PHP,不用實現(xiàn)所有的Iterator
接口的方法(并保持一個內(nèi)部指針,等等),只是實現(xiàn)IteratorAggregate
接口,讓它返回一個ArrayIterator
實例基于內(nèi)部數(shù)組:
final class Names implements IteratorAggregate { /** * @var array*/ private array $names; public function __construct(array $names) { Assert::that()->allIsString($names); $this->names = $names; } public function getIterator(): Iterator { return new ArrayIterator($this->names); } } $names = new Names([/* ... */]); foreach ($names as $name) { // ... }
以上就是PHP中數(shù)組規(guī)范和自定義集合的詳細內(nèi)容,更多請關(guān)注創(chuàng)新互聯(lián)其它相關(guān)文章!