溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

數(shù)組在php中的應(yīng)用

發(fā)布時(shí)間:2020-06-19 12:56:41 來(lái)源:億速云 閱讀:146 作者:鴿子 欄目:編程語(yǔ)言

這差不多是一個(gè)關(guān)于數(shù)組設(shè)計(jì)的風(fēng)格指南,但是把它添加到對(duì)象設(shè)計(jì)風(fēng)格指南感覺(jué)不太對(duì),因?yàn)椴皇撬械拿嫦驅(qū)ο笳Z(yǔ)言都有動(dòng)態(tài)數(shù)組。本文中的示例是用 PHP 編寫(xiě)的,因?yàn)?PHP 很像 Java(可能比較熟悉),但是使用的是動(dòng)態(tài)數(shù)組而不是內(nèi)置的集合類和接口。

使用數(shù)組作為列表

所有元素都應(yīng)該具有相同的類型

當(dāng)使用一個(gè)數(shù)組作為一個(gè)列表(一個(gè)具有特定順序的值的集合)時(shí),每個(gè)值應(yīng)該是 z 類型:

$goodList = [
    'a',
    'b'
];

$badList = [
    'a',
    1
];

一個(gè)被普遍接受的注釋列表類型的風(fēng)格是:@var array<TypeOfElement>。 確保不添加索引的類型(它總是int)。

應(yīng)該忽略每個(gè)元素的索引

PHP 將自動(dòng)為列表中的每個(gè)元素(0、1、2等)創(chuàng)建新索引。然而,你不應(yīng)該依賴這些索引,也不應(yīng)該直接使用它們。客戶端應(yīng)該依賴的列表的唯一屬性是可迭代的可計(jì)數(shù)的。

因此,可以隨意使用foreachcount(),但不要使用for循環(huán)遍歷列表中的元素:

// 好的循環(huán):
foreach ($list as $element) {
}

// 不好的循環(huán) (公開(kāi)每個(gè)元素的索引):
foreach ($list as $index => $element) {
}

// 也是不好的循環(huán) (不應(yīng)該使用每個(gè)元素的索引):
for ($i = 0; $i < count($list); $i++) {
}

(在 PHP 中,for循環(huán)甚至可能不起作用,因?yàn)榱斜碇锌赡苋鄙偎饕?,而且索引可能比列表中的元素?cái)?shù)量還要多。)

使用過(guò)濾器而不是刪除元素

你可能希望通過(guò)索引從列表中刪除元素(unset()),但是,你應(yīng)該使用array_filter()來(lái)創(chuàng)建一個(gè)新列表(沒(méi)有不需要的元素),而不是刪除元素。

同樣,你不應(yīng)該依賴于元素的索引,因此,在使用array_filter()時(shí),不應(yīng)該使用flag 參數(shù)去根據(jù)索引過(guò)濾元素,甚至根據(jù)元素和索引過(guò)濾元素。

// 好的過(guò)濾:
array_filter(
    $list, 
    function (string $element): bool { 
        return strlen($element) > 2; 
    }
);

// 不好的過(guò)濾器(也使用索引來(lái)過(guò)濾元素)
array_filter(
    $list, 
    function (int $index): bool { 
        return $index > 3;
    },
    ARRAY_FILTER_USE_KEY
);

// 不好的過(guò)濾器(同時(shí)使用索引和元素來(lái)過(guò)濾元素)
array_filter(
    $list, 
    function (string $element, int $index): bool { 
        return $index > 3 || $element === 'Include';
    },
    ARRAY_FILTER_USE_BOTH
);

使用數(shù)組作為映射

當(dāng)鍵相關(guān)的,而不是索引(0,1,2,等等)。你可以隨意使用數(shù)組作為映射(可以通過(guò)其唯一的鍵從其中檢索值)。

所有的鍵應(yīng)該是相同的類型

使用數(shù)組作為映射的第一個(gè)規(guī)則是,數(shù)組中的所有鍵都應(yīng)該具有相同的類型(最常見(jiàn)的是string類型的鍵)。

$goodMap = [
    'foo' => 'bar',
    'bar' => 'baz'
];

// 不好(使用不同類型的鍵)
$badMap = [
    'foo' => 'bar',
    1 => 'baz'
];

所有的值都應(yīng)該是相同的類型

映射中的值也是如此:它們應(yīng)該具有相同的類型。

$goodMap = [
    'foo' => 'bar',
    'bar' => 'baz'
];

// 不好(使用不同類型的值)
$badMap = [
    'foo' => 'bar',
    'bar' => 1
];

一種普遍接受的映射類型注釋樣式是: @var array<TypeOfKey, TypeOfValue>

映射應(yīng)該保持私有

列表可以安全地在對(duì)象之間傳遞,因?yàn)樗鼈兙哂泻?jiǎn)單的特征。任何客戶端都可以使用它來(lái)循環(huán)其元素,或計(jì)數(shù)其元素,即使列表是空的。映射則更難處理,因?yàn)榭蛻舳丝赡芤蕾囉跊](méi)有對(duì)應(yīng)值的鍵。這意味著在一般情況下,它們應(yīng)該對(duì)管理它們的對(duì)象保持私有。不允許客戶端直接訪問(wèn)內(nèi)部映射,而是提供 getter (可能還有 setter )來(lái)檢索值。如果請(qǐng)求的鍵不存在值,則拋出異常。但是,如果您可以保持映射及其值完全私有,那么就這樣做。

// 公開(kāi)一個(gè)列表是可以的

/**
 * @return array<User>
 */
public function allUsers(): array
{
    // ...
}

// 公開(kāi)地圖可能很麻煩

/**
 * @return array<string, User>
 */
public function usersById(): array
{ 
    // ...
}

// 相反,提供一種方法來(lái)根據(jù)其鍵檢索值

/**
 * @throws UserNotFound
 */ 
public function userById(string $id): User
{ 
    // ...
}

對(duì)具有多個(gè)值類型的映射使用對(duì)象

當(dāng)你想要在一個(gè)映射中存儲(chǔ)不同類型的值時(shí),請(qǐng)使用一個(gè)對(duì)象。定義一個(gè)類,并向其添加公共的類型化屬性,或添加構(gòu)造函數(shù)和 getter。像這樣的對(duì)象的例子是配置對(duì)象,或者命令對(duì)象:

final class SillyRegisterUserCommand
{
    public string $username;
    public string $plainTextPassword;
    public bool $wantsToReceiveSpam;
    public int $answerToIAmNotARobotQuestion;
}

這些規(guī)則的例外

有時(shí),庫(kù)或框架需要以更動(dòng)態(tài)的方式使用數(shù)組。在這些情況下,不可能(也不希望)遵循前面的規(guī)則。例如數(shù)組數(shù)據(jù),它將被存儲(chǔ)在一個(gè)數(shù)據(jù)庫(kù)表中,或者Symfony 表單配置。

自定義集合類

自定義集合類是一種非常酷的方法,最后可以和Iterator、ArrayAccess和其朋友一起使用,但是我發(fā)現(xiàn)大多數(shù)生成的代碼令人很困惑。第一次查看代碼的人必須在 PHP 手冊(cè)中查找詳細(xì)信息,即使他們是有經(jīng)驗(yàn)的開(kāi)發(fā)人員。另外,你需要編寫(xiě)更多的代碼,你必須維護(hù)這些代碼(測(cè)試、調(diào)試等)。所以在大多數(shù)情況下,我發(fā)現(xiàn)一個(gè)簡(jiǎn)單的數(shù)組,加上一些適當(dāng)?shù)念愋妥⑨專妥銐蛄?。到底什么是需要將?shù)組封裝到自定義集合對(duì)象中的強(qiáng)信號(hào)?

  • 如果你發(fā)現(xiàn)與那個(gè)數(shù)組相關(guān)的邏輯被復(fù)制了。
  • 如果你發(fā)現(xiàn)客戶端必須處理太多關(guān)于數(shù)組內(nèi)部?jī)?nèi)容的細(xì)節(jié)。

使用自定義集合類來(lái)防止重復(fù)邏輯

如果使用相同數(shù)組的多個(gè)客戶端執(zhí)行相同的任務(wù)(例如過(guò)濾、映射、減少、計(jì)數(shù)),則可以通過(guò)引入自定義集合類來(lái)消除重復(fù)。將重復(fù)的邏輯移到集合類的一個(gè)方法上,允許任何客戶端使用對(duì)集合的簡(jiǎn)單方法調(diào)用來(lái)執(zhí)行相同的任務(wù):

$names = [/* ... */];

// 在幾個(gè)地方發(fā)現(xiàn):
$shortNames = array_filter(
    $names, 
    function (string $element): bool { 
        return strlen($element) < 5; 
    }
);

// 變成一個(gè)自定義集合類:
use Assert\Assert;

final class Names
{
    /**
     * @var array<string>
     */
    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)換上使用方法的好處就是獲得了一個(gè)名稱。這使你能夠向看起來(lái)相當(dāng)復(fù)雜的array_filter()調(diào)用添加一個(gè)簡(jiǎn)短而有意義的標(biāo)簽。

使用自定義集合類來(lái)解耦客戶端

如果一個(gè)客戶端使用特定的數(shù)組并循環(huán),從選定的元素中取出一段數(shù)據(jù),并對(duì)該數(shù)據(jù)進(jìn)行處理,那么該客戶端就與所有涉及的類型緊密耦合: 數(shù)組本身、數(shù)組中元素的類型、它從所選元素中檢索的值的類型、選擇器方法的類型,等等。這種深度耦合的問(wèn)題是,在不破壞依賴于它們的客戶端的情況下,很難更改所涉及類型的任何內(nèi)容。因此,在這種情況下,你也可以將數(shù)組包裝在一個(gè)自定義 的集合類中,讓它一次性給出正確的答案,在內(nèi)部進(jìn)行必要的計(jì)算,讓客戶端與集合更加松散地耦合。

$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;
    }
}

自定義集合類的一些規(guī)則

讓我們看看在使用自定義集合類時(shí)應(yīng)用的一些規(guī)則。

讓它們不可變

對(duì)集合實(shí)例的現(xiàn)有引用在運(yùn)行某種轉(zhuǎn)換時(shí)不應(yīng)受到影響。因此,任何執(zhí)行轉(zhuǎn)換的方法都應(yīng)該返回類的一個(gè)新實(shí)例,就像我們?cè)谏厦娴睦又锌吹降哪菢?

final class Names
{
    /**
     * @var array<string>
     */
    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ù)組,則可能要映射到另一種類型的集合或簡(jiǎn)單數(shù)組。與往常一樣,請(qǐng)確保提供適當(dāng)?shù)姆祷仡愋汀?/p>

只提供實(shí)際客戶需要和使用的行為

你不必?cái)U(kuò)展泛型集合庫(kù)類,也不必自己在每個(gè)自定義集合類上實(shí)現(xiàn)泛型篩選器、映射和縮減方法,只實(shí)現(xiàn)真正需要的。如果某個(gè)方法在某一時(shí)刻不被使用,那么就刪除它。

使用 IteratorAggregate 和 ArrayIterator 來(lái)支持迭代

如果你使用 PHP,不用實(shí)現(xiàn)所有的Iterator接口的方法(并保持一個(gè)內(nèi)部指針,等等),只是實(shí)現(xiàn)IteratorAggregate接口,讓它返回一個(gè)ArrayIterator實(shí)例基于內(nèi)部數(shù)組:

final class Names implements IteratorAggregate
{
    /**
     * @var array<string>
     */
    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ī)范和自定義集合的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注億速云其它相關(guān)文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI