
PHP 代理模式讲解和代码示例
代理是一种结构型设计模式, 让你能提供真实服务对象的替代品给客户端使用。 代理接收客户端的请求并进行一些处理 (访问控制和缓存等), 然后再将请求传递给服务对象。
代理对象拥有和服务对象相同的接口, 这使得当其被传递给客户端时可与真实对象互换。
复杂度:
流行度:
使用示例: 尽管代理模式在绝大多数 PHP 程序中并不常见, 但它在一些特殊情况下仍然非常方便。 当你希望在无需修改客户代码的前提下于已有类的对象上增加额外行为时, 该模式是无可替代的。
识别方法: 代理模式会将所有实际工作委派给一些其他对象。 除非代理是某个服务的子类, 否则每个代理方法最后都应该引用一个服务对象。
概念示例
本例说明了代理设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。
index.php: 概念示例
<?php
namespace RefactoringGuru\Proxy\Conceptual;
/**
* The Subject interface declares common operations for both RealSubject and the
* Proxy. As long as the client works with RealSubject using this interface,
* you'll be able to pass it a proxy instead of a real subject.
*/
interface Subject
{
public function request(): void;
}
/**
* The RealSubject contains some core business logic. Usually, RealSubjects are
* capable of doing some useful work which may also be very slow or sensitive -
* e.g. correcting input data. A Proxy can solve these issues without any
* changes to the RealSubject's code.
*/
class RealSubject implements Subject
{
public function request(): void
{
echo "RealSubject: Handling request.\n";
}
}
/**
* The Proxy has an interface identical to the RealSubject.
*/
class Proxy implements Subject
{
/**
* @var RealSubject
*/
private $realSubject;
/**
* The Proxy maintains a reference to an object of the RealSubject class. It
* can be either lazy-loaded or passed to the Proxy by the client.
*/
public function __construct(RealSubject $realSubject)
{
$this->realSubject = $realSubject;
}
/**
* The most common applications of the Proxy pattern are lazy loading,
* caching, controlling the access, logging, etc. A Proxy can perform one of
* these things and then, depending on the result, pass the execution to the
* same method in a linked RealSubject object.
*/
public function request(): void
{
if ($this->checkAccess()) {
$this->realSubject->request();
$this->logAccess();
}
}
private function checkAccess(): bool
{
// Some real checks should go here.
echo "Proxy: Checking access prior to firing a real request.\n";
return true;
}
private function logAccess(): void
{
echo "Proxy: Logging the time of request.\n";
}
}
/**
* The client code is supposed to work with all objects (both subjects and
* proxies) via the Subject interface in order to support both real subjects and
* proxies. In real life, however, clients mostly work with their real subjects
* directly. In this case, to implement the pattern more easily, you can extend
* your proxy from the real subject's class.
*/
function clientCode(Subject $subject)
{
// ...
$subject->request();
// ...
}
echo "Client: Executing the client code with a real subject:\n";
$realSubject = new RealSubject();
clientCode($realSubject);
echo "\n";
echo "Client: Executing the same client code with a proxy:\n";
$proxy = new Proxy($realSubject);
clientCode($proxy);
Output.txt: 执行结果
Client: Executing the client code with a real subject:
RealSubject: Handling request.
Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.
真实世界示例
可供使用的代理类型有无数种: 缓存、 日志、 访问控制和延迟初始化等。 本例演示了代理模式如何通过对结果进行缓存来提高下载器对象的性能。
index.php: 真实世界示例
<?php
namespace RefactoringGuru\Proxy\RealWorld;
/**
* The Subject interface describes the interface of a real object.
*
* The truth is that many real apps may not have this interface clearly defined.
* If you're in that boat, your best bet would be to extend the Proxy from one
* of your existing application classes. If that's awkward, then extracting a
* proper interface should be your first step.
*/
interface Downloader
{
public function download(string $url): string;
}
/**
* The Real Subject does the real job, albeit not in the most efficient way.
* When a client tries to download the same file for the second time, our
* downloader does just that, instead of fetching the result from cache.
*/
class SimpleDownloader implements Downloader
{
public function download(string $url): string
{
echo "Downloading a file from the Internet.\n";
$result = file_get_contents($url);
echo "Downloaded bytes: " . strlen($result) . "\n";
return $result;
}
}
/**
* The Proxy class is our attempt to make the download more efficient. It wraps
* the real downloader object and delegates it the first download calls. The
* result is then cached, making subsequent calls return an existing file
* instead of downloading it again.
*
* Note that the Proxy MUST implement the same interface as the Real Subject.
*/
class CachingDownloader implements Downloader
{
/**
* @var SimpleDownloader
*/
private $downloader;
/**
* @var string[]
*/
private $cache = [];
public function __construct(SimpleDownloader $downloader)
{
$this->downloader = $downloader;
}
public function download(string $url): string
{
if (!isset($this->cache[$url])) {
echo "CacheProxy MISS. ";
$result = $this->downloader->download($url);
$this->cache[$url] = $result;
} else {
echo "CacheProxy HIT. Retrieving result from cache.\n";
}
return $this->cache[$url];
}
}
/**
* The client code may issue several similar download requests. In this case,
* the caching proxy saves time and traffic by serving results from cache.
*
* The client is unaware that it works with a proxy because it works with
* downloaders via the abstract interface.
*/
function clientCode(Downloader $subject)
{
// ...
$result = $subject->download("http://example.com/");
// Duplicate download requests could be cached for a speed gain.
$result = $subject->download("http://example.com/");
// ...
}
echo "Executing client code with real subject:\n";
$realSubject = new SimpleDownloader();
clientCode($realSubject);
echo "\n";
echo "Executing the same client code with a proxy:\n";
$proxy = new CachingDownloader($realSubject);
clientCode($proxy);
Output.txt: 执行结果
Executing client code with real subject:
Downloading a file from the Internet.
Downloaded bytes: 1270
Downloading a file from the Internet.
Downloaded bytes: 1270
Executing the same client code with a proxy:
CacheProxy MISS. Downloading a file from the Internet.
Downloaded bytes: 1270
CacheProxy HIT. Retrieving result from cache.