使用__get()(魔术)模拟只读属性和延迟加载 [英] Using __get() (magic) to emulate readonly properites and lazy-loading

查看:82
本文介绍了使用__get()(魔术)模拟只读属性和延迟加载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 __get()来制作一些属性动态"(仅在需要时初始化它们).这些假"属性存储在私有数组属性中,我正在__get中对其进行检查.

I'm using __get() to make some of my properties "dynamic" (initialize them only when requested). These "fake" properties are stored inside a private array property, which I'm checking inside __get.

无论如何,您最好为这些属性中的每一个创建方法而不是在switch语句中这样做吗?

Anyway, do you think it's better idea to create methods for each of these proprties instead of doing it in a switch statement?

我只关心性能,@ Gordon提到的其他内容对我而言并不那么重要:

I'm only concerned about performance, other stuff that @Gordon mentioned are not that important to me:

  • 不需要增加复杂性-并没有真正增加我的应用程序复杂性
  • 脆弱的非显而易见的API-我特别希望我的API被隔离";该文档应告诉其他人如何使用它:P

这是我进行的测试,这些测试使我认为性能不足的说法是不合理的:

So here are the tests that I made, which make me think that the performance hit agument is unjustified:

50.000次调用的结果(在PHP 5.3.9上):

Results for 50.000 calls (on PHP 5.3.9):

(t1 =带有开关的魔术,t2 =吸气剂,t3 =带有进一步的吸气剂的魔术)

(t1 = magic with switch, t2 = getter, t3 = magic with further getter call)

不确定t3上暨"是什么意思.它不能是累积时间,因为t2那时应该有2K ...

Not sure what the "Cum" thing mean on t3. It cant be cumulative time because t2 should have 2K then...

代码:

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:为什么我要在标准的getter方法上使用__get()?唯一的原因是api美;因为我没有看到任何实际的缺点,所以我认为这是值得的:P

ps: why do I want to use __get() over standard getter methods? the only reason is the api beauty; because i don't see any real disadvantages, I guess it's worth it :P

这次我用微时间测量一些平均值:

This time I used microtime to measure some averages:

PHP 5.2.4和5.3.0(相似的结果):

PHP 5.2.4 and 5.3.0 (similar results):

t1 - 0.12s
t2 - 0.08s
t3 - 0.24s

PHP 5.3.9,在激活xdebug的情况下,它是如此之慢:

PHP 5.3.9, with xdebug active this is why it's so slow:

t1 - 1.34s
t2 - 1.26s
t3-  5.06s

禁用xdebug的PHP 5.3.9:

PHP 5.3.9 with xdebug disabled:

t1 - 0.30
t2 - 0.25
t3 - 0.86


另一种方法:


Another method:

 // 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.1s(比标准getter快12倍),以及Gordon提出的可扩展性问题.您可以简单地在子类中覆盖getter.

A public property with the requested name will be created dynamically after the first __get call. This solves speed issues - getting 0.1s in PHP 5.3 (it's 12 times faster then standard getter), and the extensibility issue raised by Gordon. You can simply override the getter in the child class.

缺点是该属性变为可写:(

The disadvantage is that the property becomes writable :(

推荐答案

这是Zend Debugger在Win7机器上使用PHP 5.3.6的Zend Debugger报告的代码结果:

Here is the results of your code as reported by Zend Debugger with PHP 5.3.6 on my Win7 machine:

如您所见,对__get方法的调用比常规调用慢很多(3-4倍).对于总共5万次通话,我们仍处理不到1秒的时间,因此在小规模使用时可以忽略不计.但是,如果您打算围绕魔术方法构建整个代码,则将需要分析最终应用程序,以查看它是否仍然可以忽略不计.

As you can see, the calls to your __get methods are a good deal (3-4 times) slower than the regular calls. We are still dealing with less than 1s for 50k calls in total, so it is negligible when used on a small scale. However, if your intention is to build your entire code around magic methods, you will want to profile the final application to see if it's still negligible.

对于相当乏味的性能方面来说,太多了​​.现在,让我们看一下您认为不那么重要"的内容.我要强调一点,因为它实际上比性能方面重要得多.

So much for the rather uninteresting performance aspect. Now let's take a look at what you consider "not that important". I'm going to stress that because it actually is much more important than the performance aspect.

关于您编写​​的不必要的增加的复杂性

Regarding Uneeded Added Complexity you write

它并没有真正增加我的应用程序复杂性

it doesn't really increase my app complexity

当然可以.通过查看代码的嵌套深度,您可以轻松发现它.好的代码留在左侧.您的if/switch/case/if有四个层次.这意味着存在更多可能的执行路径,这将导致更高的循环复杂度,这意味着难度更高保持和理解.

Of course it does. You can easily spot it by looking at the nesting depth of your code. Good code stays to the left. Your if/switch/case/if is four levels deep. This means there is more possible execution pathes and that will lead to a higher Cyclomatic Complexity, which means harder to maintain and understand.

这里是您的A类的数字(没有常规的Getter.输出从PHPLoc ):

Here is numbers for your class A (w\out the regular Getter. Output is shortened from 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.另外,由于所有逻辑都在开关/盒内部,而不是将其划分为离散的单元(例如,逻辑单元),因此它将使您的代码变成程序混乱.单一的吸气剂.

A value of 4.00 means this is already at the edge to moderate complexity. This number increases by 2 for every additional case you put into your switch. In addition, it will turn your code into a procedural mess because all the logic is inside the switch/case instead of dividing it into discrete units, e.g. single Getters.

一个Getter,即使是一个懒惰的加载器,也不需要复杂.考虑使用简单的旧式PHP Getter的同一类:

A Getter, even a lazy loading one, does not need to be moderately complex. Consider the same class with a plain old PHP Getter:

class Foo
{
    protected $bar;
    public function getBar()
    {
        // Lazy Initialization
        if ($this->bar === null) {
            $this->bar = new Bar;
        }
        return $this->bar;
    }
}

在此上运行PHPLoc将为您带来更好的循环复杂性

Running PHPLoc on this will give you a much better Cyclomatic Complexity

Lines of Code (LOC):                                 11
  Cyclomatic Complexity / Lines of Code:           0.09
  Cyclomatic Complexity / Number of Methods:       2.00

您添加的每个普通旧Getter的总和将保持为2.

And this will stay at 2 for every additional plain old Getter you add.

此外,还要考虑到,当您想使用变体的子类型时,必须重载__get并复制并粘贴整个switch/case块以进行更改,而对于普通的旧Getter,您只需重载您需要更改的吸气剂.

Also, take into account that when you want to use subtypes of your variant, you will have to overload __get and copy and paste the entire switch/case block to make changes, while with a plain old Getter you simply overload the Getters you need to change.

是的,添加所有Getter的工作要更多,但它也要简单得多,最终会导致代码更易于维护,并且还具有为您提供显式API的好处,这将使我们引向您的其他声明

Yes, it's more typing work to add all the Getters, but it is also much simpler and will eventually lead to more maintainable code and also has the benefit of providing you with an explicit API, which leads us to your other statement

我特别希望我的API被隔离";该文档应该告诉其他人如何使用它:P

I specifically want my API to be "isolated"; The documentation should tell others how to use it :P

我不知道您所说的隔离"是什么意思,但是如果您的API无法表达其含义,那么它就是糟糕的代码.如果由于您的API不能告诉我如何通过查看与之进行接口而不得不阅读您的文档,则说明您做错了.您正在混淆代码.在数组中声明属性而不是在类级别(它们所属的地方)声明它们会迫使您为其编写文档,这是额外的,多余的工作.好的代码易于阅读和自我记录.考虑购买罗伯特·马丁(Robert Martin)的书清洁代码".

I don't know what you mean by "isolated" but if your API cannot express what it does, it is poor code. If I have to read your documentation because your API does not tell me how I can interface with it by looking at it, you are doing it wrong. You are obfuscating the code. Declaring properties in an array instead of declaring them at the class level (where they belong) forces you to write documentation for it, which is additional and superfluous work. Good code is easy to read and self documenting. Consider buying Robert Martin's book "Clean Code".

话虽如此,当你说

唯一的原因是api的美;

the only reason is the api beauty;

然后我说:然后不要使用__get,因为它会产生相反的效果.这会使API变得难看.魔术是复杂且非显而易见的,而这正是导致那些WTF时刻的原因:

then I say: then don't use __get because it will have the opposite effect. It will make the API ugly. Magic is complicated and non-obvious and that's exactly what leads to those WTF moments:

现在结束:

我没有看到任何真正的劣势,我想这是值得的

i don't see any real disadvantages, I guess it's worth it

您希望现在能看到它们.这不值得.

You hopefully see them now. It's not worth it.

有关延迟加载的其他方法,请参见Martina Fowler的PoEAA中的各种延迟加载模式:

For additional approaches to Lazy Loading, see the various Lazy Loading patterns from Martin Fowler's PoEAA:

懒惰负载有四个主要变体. 惰性初始化使用特殊的标记值(通常为null)表示未加载字段.每次对该字段的访问都会检查该字段的标记值,如果已卸载,则将其加载. 虚拟代理是具有与真实对象相同接口的对象.第一次调用其方法之一时,它将加载实际的对象,然后进行委托. Value Holder 是具有getValue方法的对象.客户端调用getValue获取真实对象,第一个调用触发加载. 是没有任何数据的真实对象.第一次调用方法时,Ghost会将全部数据加载到其字段中.

There are four main varieties of lazy load. Lazy Initialization uses a special marker value (usually null) to indicate a field isn't loaded. Every access to the field checks the field for the marker value and if unloaded, loads it. Virtual Proxy is an object with the same interface as the real object. The first time one of its methods are called it loads the real the object and then delegates. Value Holder is an object with a getValue method. Clients call getValue to get the real object, the first call triggers the load. A ghost is the real object without any data. The first time you call a method the ghost loads the full data into its fields.

这些方法有些微妙的变化,并且需要权衡取舍.您也可以使用组合方法.本书包含完整的讨论和示例.

These approaches vary somewhat subtly and have various trade-offs. You can also use combination approaches. The book contains the full discussion and examples.

这篇关于使用__get()(魔术)模拟只读属性和延迟加载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆