我认为你不能对所有东西都有一个通用的惰性评估容器。
让我们首先讨论一下你真正拥有的东西。我不认为这是懒惰的评价。延迟评估被定义为将评估延迟到真正需要该值的点,并将已评估的值与对该值的进一步请求共享。
我想到的典型例子是数据库连接。您将准备好一切,以便在需要时能够使用该连接,但只有在确实需要数据库查询时,才会创建连接,然后与后续查询共享。
典型的实现是将连接字符串传递给构造函数,将其存储在内部,当调用查询方法时,首先调用返回连接句柄的方法,该方法将创建并保存该连接句柄如果不存在则为字符串。以后对该对象的调用将重用现有的连接。
这样的数据库对象将有资格对数据库连接进行延迟评估:它仅在真正需要时创建,然后为所有其他查询共享。
当我查看您的实现时,它不符合“仅在真正需要时评估”的条件,它只会存储曾经创建的值。所以它实际上只是某种缓存。
它也没有真正解决普遍只在全局范围内评估昂贵计算的问题。如果您有两个实例,您将运行两次昂贵的函数。但另一方面,不对其进行两次评估会引入全局状态——除非明确声明,否则这应该被认为是一件坏事。通常它会使代码很难正确测试。我个人会避免这种情况。
可以将实际数据方法与数据计算方法放在不同的对象中吗?
如果您查看 Zend 框架如何提供缓存模式 ( \Zend\Cache\Pattern\{Callback,Class,Object}Cache
),您会发现真正的工作类正在获得一个包装在其周围的装饰器。获取存储的值并读回它们的所有内部工作都是在内部处理的,您可以像以前一样从外部调用您的方法。
缺点是您没有原始类类型的对象。因此,如果您使用类型提示,则不能传递修饰的缓存对象而不是原始对象。解决方案是实现一个接口。原始类用真正的功能实现它,然后你创建另一个类来扩展缓存装饰器并实现接口。该对象将通过类型提示检查,但您必须手动实现所有接口方法,这些方法无非是将调用传递给否则会拦截它们的内部魔术函数。
interface Foo
{
public function foo();
}
class FooExpensive implements Foo
{
public function foo()
{
sleep(100);
return "bar";
}
}
class FooCached extends \Zend\Cache\Pattern\ObjectPattern implements Foo
{
public function foo()
{
//internally uses instance of FooExpensive to calculate once
$args = func_get_args();
return $this->call(__FUNCTION__, $args);
}
}
我发现在 PHP 中至少没有这两个类和一个接口就不可能实现缓存(但另一方面,针对接口实现是一件好事,它不应该打扰你)。您不能简单地直接使用本机缓存对象。
可以使用带有嵌套数组的缓存来使用带参数的计算方法吗?
参数在上述实现中起作用,它们用于缓存键的内部生成。你可能应该看看这个\Zend\Cache\Pattern\CallbackCache::generateCallbackKey
方法。
在 PHP 中,可以使用魔术方法(__get() 或 __call())作为主要检索方法。结合类文档块中的“@property”,这允许每个“虚拟”属性的类型提示。
魔法方法是邪恶的。文档块应该被认为是过时的,因为它不是真正的工作代码。虽然我发现在一个非常容易理解的值对象代码中使用魔法 getter 和 setter 是可以接受的,这将允许在任何属性中存储任何值,就像.stdClass
__call
我经常使用诸如“get_someValue()”之类的方法名称,其中“someValue”是实际的键,以区别于常规方法。
我认为这违反了 PSR-1:“4.3. 方法:方法名称必须在 . 中声明camelCase()
。” 是否有理由将这些方法标记为特殊的?它们很特别吗?确实返回值,不是吗?
是否可以将数据计算分配给多个对象,以获得某种关注点分离?
如果您缓存对象的复杂构造,这是完全可能的。
可以预初始化一些值吗?
这不应该是缓存的问题,而是实现本身的问题。不执行昂贵的计算,而是返回预设值有什么意义?如果这是一个真实的用例(例如,如果参数超出定义的范围,则立即返回 NULL),它必须是实现本身的一部分。在这种情况下,您不应依赖对象周围的附加层来返回值。
存储动态编程的中间值是一个合法的用例吗?
你有动态规划问题吗?您链接的维基百科页面上有这句话:
为了使动态规划适用,问题必须具有两个关键属性:最优子结构和重叠子问题。如果可以通过组合非重叠子问题的最优解来解决问题,则该策略称为“分而治之”。
我认为已经有一些现有的模式似乎可以解决您示例中的惰性评估部分:Singleton、ServiceLocator、Factory。(我不是在这里推广单身人士!)
还有“承诺”的概念:返回的对象承诺稍后如果被要求返回实际值,但只要现在不需要该值,就会充当可以传递的值替换。您可能想阅读这篇博文:http: //blog.ircmaxell.com/2013/01/promise-for-clean-code.html
在 PHP 中实现这一点的最佳实践是什么?“细节”里的一些东西是不是又丑又丑?
您使用了一个可能接近斐波那契示例的示例。我不喜欢该示例的方面是您使用单个实例来收集所有值。在某种程度上,你在这里聚合了全局状态——这可能就是整个概念的意义所在。但是全局状态是邪恶的,我不喜欢那个额外的层。而且您还没有真正解决足够的参数问题。
bar()
我想知道为什么里面真的有两个电话foo()
?更明显的方法是将结果直接复制到 中foo()
,然后“添加”它。
总而言之,直到现在我都没有印象深刻。在这个简单的层面上,我无法预料到这样一个通用解决方案的真实用例。我确实喜欢 IDE 自动建议支持,但我不喜欢鸭式打字(传递一个仅模拟兼容但无法确保实例的对象)。