我正在使用__get()使我的某些属性“动态”(仅在请求时初始化它们)。这些“假”属性存储在私有数组属性中,我在__get。
中检查无论如何,您认为为每个属性创建方法更好,而不是在switch语句中进行吗?
我只关心表现,@戈登提到的其他东西对我来说并不重要:
所以这里是我做的测试,这让我觉得表现不佳是不合理的:
50.000次调用的结果(在PHP 5.3.9上):
(t1 =魔术与开关,t2 = getter,t3 =魔术与进一步的getter调用)
不确定t3上“Cum”的含义是什么。它不能累积时间因为t2应该有2K然后......
代码:
class B{}
class A{
protected
$props = array(
'test_obj' => false,
);
// magic
function __get($name){
if(isset($this->props[$name])){
switch($name){
case 'test_obj':
if(!($this->props[$name] instanceof B))
$this->props[$name] = new B;
break;
}
return $this->props[$name];
}
trigger_error('property doesnt exist');
}
// standard getter
public function getTestObj(){
if(!($this->props['test_obj'] instanceof B))
$this->props['test_obj'] = new B;
return $this->props['test_obj'];
}
}
class AA extends A{
// magic
function __get($name){
$getter = "get".str_replace('_', '', $name); // give me a break, its just a test :P
if(method_exists($this, $getter))
return $this->$getter();
trigger_error('property doesnt exist');
}
}
function t1(){
$obj = new A;
for($i=1;$i<50000;$i++){
$a = $obj->test_obj;
}
echo 'done.';
}
function t2(){
$obj = new A;
for($i=1;$i<50000;$i++){
$a = $obj->getTestObj();
}
echo 'done.';
}
function t3(){
$obj = new AA;
for($i=1;$i<50000;$i++){
$a = $obj->test_obj;
}
echo 'done.';
}
t1();
t2();
t3();
ps:为什么我要使用__get()而不是标准的getter方法?唯一的原因是api美;因为我没有看到任何真正的缺点,我想这是值得的:P
这次我用microtime来测量一些平均值:
PHP 5.2.4和5.3.0(类似结果):
t1 - 0.12s
t2 - 0.08s
t3 - 0.24s
PHP 5.3.9,xdebug处于活动状态,这就是它如此缓慢的原因:
t1 - 1.34s
t2 - 1.26s
t3- 5.06s
PHP 5.3.9,禁用xdebug:
t1 - 0.30
t2 - 0.25
t3 - 0.86
<小时/> 另一种方法:
// magic
function __get($name){
$getter = "get".str_replace('_', '', $name);
if(method_exists($this, $getter)){
$this->$name = $this->$getter(); // <-- create it
return $this->$name;
}
trigger_error('property doesnt exist');
}
在第一次__get调用之后,将动态创建具有所请求名称的公共属性。这解决了速度问题 - 在PHP 5.3中获得0.1秒(它比标准getter快12倍),以及Gordon提出的可扩展性问题。您可以简单地覆盖子类中的getter。
缺点是该属性变得可写:(
答案 0 :(得分:18)
以下是Zend Debugger在我的Win7机器上使用PHP 5.3.6报告的代码结果:
正如您所看到的,对__get
方法的调用比常规调用慢很多(3-4倍)。对于总共50k的呼叫,我们仍然处理不到1s,因此在小规模使用时可以忽略不计。但是,如果您的目的是围绕魔术方法构建整个代码,那么您需要分析最终的应用程序,看它是否仍然可以忽略不计。
非常无趣的表现方面。现在让我们来看看你认为“不重要”的东西。我要强调的是,因为它实际上比性能方面更重要。
关于你所写的不需要的复杂性
它并没有真正提高我的应用复杂性
当然可以。您可以通过查看代码的嵌套深度轻松发现它。好的代码留在左边。你的if / switch / case / if是四级深度。这意味着有更多可能的执行路径,这将导致更高的Cyclomatic Complexity,这意味着更难维护和理解。
以下是A类的数字(与普通的Getter相比。输出缩短为PHPLoc):
Lines of Code (LOC): 19
Cyclomatic Complexity / Lines of Code: 0.16
Average Method Length (NCLOC): 18
Cyclomatic Complexity / Number of Methods: 4.00
值为4.00表示这已经处于边缘以缓和复杂性。放入交换机的每个附加情况下,此数字增加2。此外,它会将您的代码变成程序混乱,因为所有逻辑都在交换机/案例中,而不是将其划分为离散单元,例如单个Getters。
一个Getter,即使是一个懒惰的加载器,也不需要中等复杂。考虑使用普通的旧PHP Getter的同一个类:
class Foo
{
protected $bar;
public function getBar()
{
// Lazy Initialization
if ($this->bar === null) {
$this->bar = new Bar;
}
return $this->bar;
}
}
在此上运行PHPLoc将为您提供更好的Cyclomatic Complexity
Lines of Code (LOC): 11
Cyclomatic Complexity / Lines of Code: 0.09
Cyclomatic Complexity / Number of Methods: 2.00
对于你添加的每个额外的普通旧Getter,这将保持在2。
另外,考虑到当你想要使用变体的子类型时,你必须重载__get
并复制并粘贴整个开关/案例块以进行更改,而使用普通的旧Getter你只需重载您需要更改的Getters。
是的,添加所有Getters的打字工作更多,但它也更简单,最终会带来更易维护的代码,并且还有一个明确的API,可以引导我们进行另一个声明< / p>
我特别希望我的API“孤立”;文档应告诉其他人如何使用它:P
我不知道你所说的“孤立”是什么意思,但如果你的API无法表达它的作用,那就是糟糕的代码。如果我必须阅读您的文档,因为您的API没有通过查看它告诉我如何与它进行交互,那么您做错了。你正在混淆代码。声明数组中的属性而不是在类级别(它们所属的位置)声明它们会强制您为它编写文档,这是额外的和多余的工作。好的代码易于阅读和自我记录。考虑购买罗伯特·马丁的书“清洁代码”。
据说,当你说
时唯一的原因是api美;
然后我说:然后不要使用__get
,因为它会产生相反的效果。它会使API变得丑陋。魔术是复杂而不明显的,这正是导致WTF时刻的原因:
现在就结束:
我没有看到任何真正的缺点,我想这是值得的
你希望现在能看到它们。这不值得。
有关延迟加载的其他方法,请参阅various Lazy Loading patterns from Martin Fowler's PoEAA:
懒惰载荷主要有四种。 延迟初始化使用特殊标记值(通常为null)来指示未加载字段。对字段的每次访问都会检查字段中的标记值,如果已卸载,则加载它。 虚拟代理是一个与真实对象具有相同接口的对象。第一次调用其中一个方法时,它会加载真实的对象然后委托。 值持有者是具有getValue方法的对象。客户端调用getValue来获取真实对象,第一个调用触发负载。 ghost 是没有任何数据的真实对象。第一次调用方法时,ghost会将完整数据加载到其字段中。
这些方法略有不同,并有各种权衡取舍。您也可以使用组合方法。这本书包含完整的讨论和例子。
答案 1 :(得分:2)
如果您的班级名称和$prop
中的键名匹配,则可以这样做:
class Dummy {
private $props = array(
'Someobject' => false,
//etc...
);
function __get($name){
if(isset($this->props[$name])){
if(!($this->props[$name] instanceof $name)) {
$this->props[$name] = new $name();
}
return $this->props[$name];
}
//trigger_error('property doesnt exist');
//Make exceptions, not war
throw new Exception('Property doesn\'t exist');
}
}
即使资本化不匹配,只要它遵循相同的模式,它就可以工作。如果第一个字母始终大写,您可以使用ucfirst()
来获取类名。
修改强>
使用普通方法可能更好。在getter中有一个开关,特别是当你试图获得的每个东西执行的代码不同时,实际上会破坏getter的目的,以免你不必重复代码。采取简单的方法:
class Something {
private $props = array('Someobject' => false);
public getSomeobject() {
if(!($this->props['Someobject'] instanceof Someobject)) {
//Instantiate and do extra stuff
}
return $this->props['Someobject'];
}
public getSomeOtherObject() {
//etc..
}
}
答案 2 :(得分:2)
我正在使用__get()使我的一些属性“动态”(仅在请求时初始化它们)。这些“假”属性存储在私有数组属性中,我在__get。
中检查无论如何,您认为为每个属性创建方法更好,而不是在switch语句中进行吗?
你提出问题的方式我不认为这实际上与任何人的想法有关。谈谈想法,首先必须清楚你想在这里解决哪个问题。
魔术_get
以及普通getter methods都有助于提供价值。但是,在PHP中你不能做的就是创建一个只读属性。
如果你需要一个只读属性,那么到目前为止你只能使用PHP中的魔术_get
函数(替代方案在RFC中)。
如果您对访问器方法没有问题,并且您担心键入方法的代码,请使用更好的IDE,如果您真的关心该编写方面,那么可以为您执行此操作。
如果这些属性不需要具体,那么你可以保持它们的动态,因为更具体的界面将是一个无用的细节,只会使你的代码比它需要的更复杂,因此违反了常见的面向对象设计原则。 / p>
然而,动态或魔法也可能是你做错事的标志。而且很难调试。所以你真的应该知道你在做什么。这需要你使你想解决的问题更加具体,因为这在很大程度上取决于对象的类型。
速度是你不应该测试孤立的东西,它不会给你很好的建议。你问题的速度听起来更像药物;)但服用这种药物不会给你明智的决定权。
答案 3 :(得分:1)
使用__get()
被认为是性能损失。因此,如果您的参数列表是静态/固定且非常长,那么为每个参数创建方法并跳过__get()
会更好。例如:
public function someobject() {
if(!($this->props[$name] instanceof Someobject))
$this->props[$name] = new Someobject;
// do stuff to initialize someobject
}
if (count($argv = func_get_args())) {
// do stuff to SET someobject from $a[0]
}
return $this->props['someobject'];
}
为了避免使用魔术方法,你必须像这样改变你使用它的方式
$bar = $foo->someobject; // this won't work without __get()
$bar = $foo->someobject(); // use this instead
$foo->someobject($bar); // this is how you would set without __set()
修改强>
编辑,正如亚历克斯指出的那样,性能命中小毫秒。您可以尝试两种方式并执行一些基准测试,或者只使用__get,因为它不太可能对您的应用程序产生重大影响。