如果您不需要 Type Safety,则可以使用通用缓存装饰器:
class Cached
{
public function __construct($instance, $cacheDir = null)
{
$this->instance = $instance;
$this->cacheDir = $cacheDir === null ? sys_get_temp_dir() : $cacheDir;
}
public function defineCachingForMethod($method, $timeToLive)
{
$this->methods[$method] = $timeToLive;
}
public function __call($method, $args)
{
if ($this->hasActiveCacheForMethod($method, $args)) {
return $this->getCachedMethodCall($method, $args);
} else {
return $this->cacheAndReturnMethodCall($method, $args);
}
}
// … followed by private methods implementing the caching
然后,您将需要缓存的实例包装到此装饰器中,如下所示:
$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);
显然,$cachedInstance
没有foo()
方法。这里的诀窍是利用魔法__call
方法来拦截对不可访问或不存在的方法的所有调用,并将它们委托给装饰实例。通过这种方式,我们通过装饰器公开了装饰实例的整个公共 API。
如您所见,该__call
方法还包含检查是否为该方法定义的缓存的代码。如果是这样,它将返回缓存的方法调用。如果没有,它将调用实例并缓存返回。
或者,您将专用的 CacheBackend 传递给装饰器,而不是在装饰器本身中实现缓存。然后,装饰器将仅作为被装饰实例和后端之间的中介。
这种通用方法的缺点是您的缓存装饰器将没有装饰实例的类型。当您的消费代码需要 Instance 类型的实例时,您将收到错误。
如果您需要类型安全的装饰器,则需要使用“经典”方法:
- 创建装饰实例公共 API 的接口。您可以手动完成,或者,如果工作量很大,请使用我的Interface Distiller)
- 将期望装饰实例的每个方法上的 TypeHint 更改为接口
- 让 Decorated 实例实现它。
- 让装饰器实现它并将任何方法委托给装饰实例
- 修改所有需要缓存的方法
- 对所有想要使用装饰器的类重复此操作
简而言之
class CachedInstance implements InstanceInterface
{
public function __construct($instance, $cachingBackend)
{
// assign to properties
}
public function foo()
{
// check cachingBackend whether we need to delegate call to $instance
}
}
缺点是工作量更大。您需要为每个应该使用缓存的类执行此操作。您还需要将对缓存后端的检查放入每个函数(代码重复),并将不需要缓存的任何调用委托给装饰实例(繁琐且容易出错)。