在PHP中实现方法结果缓存的装饰器模式的最佳方法

Mar*_*ian 5 php oop caching design-patterns decorator

我有一组习惯用相同的参数重复调用的类.这些方法通常运行数据库请求并构建对象数组等,因此为了减少这种重复,我构建了一些优化的缓存方法.这些使用如下:

在应用缓存之前:

public function method($arg1, $arg2) {
$result = doWork();
return $result;
}
Run Code Online (Sandbox Code Playgroud)

应用缓存后:

public function method($arg1, $arg2, $useCached=true) {
if ($useCached) {return $this->tryCache();}
$result = doWork();
return $this->cache($result);
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我现在离开了稍微费力的任务,手动将其添加到所有方法 - 我相信这是装饰器模式的用例,但我无法弄清楚如何在PHP中以更简单的方式实现它这个案例.

这样做的最好方法是什么,希望这些类中的所有方法可以自动执行此操作,或者我只需要在方法中添加一行等等?

我已经看过了覆盖return语句的方法,但是看不到任何东西.

谢谢!

Gor*_*don 10

如果您不需要Type Safety,则可以使用通用的Cache Decorator:

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
Run Code Online (Sandbox Code Playgroud)

然后,您将需要缓存的实例包装到此Decorator中,如下所示:

$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);
Run Code Online (Sandbox Code Playgroud)

显然,$cachedInstance没有foo()方法.这里的技巧是利用魔术__call方法拦截对不可访问或不存在的方法的所有调用,并将它们委托给装饰实例.这样我们就可以通过Decorator公开装饰实例的整个公共API.

如您所见,该__call方法还包含用于检查是否存在为该方法定义的缓存的代码.如果是这样,它将返回缓存的方法调用.如果没有,它将调用实例并缓存返回.

或者,您将专用的CacheBackend传递给Decorator,而不是在装饰器本身中实现缓存.然后,Decorator将仅作为装饰实例和后端之间的介体.

这种通用方法的缺点是您的缓存装饰器不具有装饰实例的类型.当您的消费代码需要Instance类型的实例时,您将收到错误.


如果您需要类型安全的装饰器,则需要使用"经典"方法:

  1. 创建装饰实例公共API的接口.您可以手动执行此操作,如果需要大量工作,请使用我的Interface Distiller)
  2. 将期望装饰实例的每个方法上的TypeHints更改为Interface
  3. 让装饰实例实现它.
  4. 让Decorator实现它并将任何方法委托给装饰实例
  5. 修改所有需要缓存的方法
  6. 对要使用装饰器的所有类重复此操作

简而言之

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
    }
}
Run Code Online (Sandbox Code Playgroud)

缺点是,它是更多的工作.您需要为每个应该使用缓存的类执行此操作.您还需要将检查放入缓存后端到每个函数(代码复制),以及将任何不需要缓存的调用委托给装饰实例(繁琐且容易出错).