代理模式

目的

为一些开销昂贵和无法复制的东西提供了一个访问接口。

使用场景

  • Doctrine2 内部使用代理来实现一些框架功能(如:延迟实例化),而用户仍然使用自己的实体类,不会使用也不会关心代理的具体细节。

UML 类图

代理模式类图

代码

BankAccount.php

1
2
3
4
5
6
7
8
9
10
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Proxy;

interface BankAccount
{
public function deposit(int $amount);

public function getBalance(): int;
}

HeavyBankAccount.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Proxy;

class HeavyBankAccount implements BankAccount
{
/**
* @var int[]
*/
private array $transactions = [];

public function deposit(int $amount)
{
$this->transactions[] = $amount;
}

public function getBalance(): int
{
// this is the heavy part, imagine all the transactions even from
// years and decades ago must be fetched from a database or web service
// and the balance must be calculated from it

return (int) array_sum($this->transactions);
}
}

BankAccountProxy.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Proxy;

class BankAccountProxy extends HeavyBankAccount implements BankAccount
{
private ?int $balance = null;

public function getBalance(): int
{
// because calculating balance is so expensive,
// the usage of BankAccount::getBalance() is delayed until it really is needed
// and will not be calculated again for this instance

if ($this->balance === null) {
$this->balance = parent::getBalance();
}

return $this->balance;
}
}

测试

Tests/ProxyTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Proxy\Tests;

use DesignPatterns\Structural\Proxy\BankAccountProxy;
use PHPUnit\Framework\TestCase;

class ProxyTest extends TestCase
{
public function testProxyWillOnlyExecuteExpensiveGetBalanceOnce()
{
$bankAccount = new BankAccountProxy();
$bankAccount->deposit(30);

// this time balance is being calculated
$this->assertSame(30, $bankAccount->getBalance());

// inheritance allows for BankAccountProxy to behave to an outsider exactly like ServerBankAccount
$bankAccount->deposit(50);

// this time the previously calculated balance is returned again without re-calculating it
$this->assertSame(30, $bankAccount->getBalance());
}
}