直接调用变量属性与 getter/setters - OOP 设计

发布于 2024-11-11 13:26:10 字数 675 浏览 2 评论 0原文

我知道这可能是主观的,但我阅读了Google for PHP 的优化页面,他们建议使用直接变量属性,无需 getter 和 setter。可以理解的是,我看到了这方面的性能提升,但这真的是一个值得遵循的良好设计实践吗?

他们使用 getter/setter 的示例:

class dog {
  public $name = '';

  public function setName($name) {
    $this->name = $name;
  }

  public function getName() {
    return $this->name;
  }
}

$rover = new dog();
$rover->setName('rover');
echo $rover->getName();

建议的优化:

$rover = new dog();
$rover->name = 'rover';
echo $rover->name;

这将是我的设计过程中的一个值得欢迎的变化,因为我看到 getter/setter 的需要消失了,但是这样做可能会出现哪些其他障碍/好处?

I know this is probably subjective but I read this optimization page from Google for PHP and they suggest use the variable property directly without the need of getters and setters. Understandably I see the performance gain in this but is this really a good design practice to follow?

Their Example using getter/setter:

class dog {
  public $name = '';

  public function setName($name) {
    $this->name = $name;
  }

  public function getName() {
    return $this->name;
  }
}

$rover = new dog();
$rover->setName('rover');
echo $rover->getName();

Suggested Optimization:

$rover = new dog();
$rover->name = 'rover';
echo $rover->name;

This would be a welcome change in my design process as I see the need for getters/setters going away, but what other hurdles/benefits might occur in doing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

快乐很简单 2024-11-18 13:26:10

这对我来说是一个值得欢迎的改变
我认为需要的设计过程
getter/setter 消失了,但是什么
其他障碍/好处可能会出现在
做这个?

您失去了对特定属性实现特殊获取/设置逻辑的能力。对于标量(字符串、整数、布尔值)的属性来说,这可能没有问题。但是,如果您有一个延迟加载类实例的属性怎么办?

class Document
{
    protected $_createdBy;

    public function getCreatedBy()
    {
        if (is_integer($this->_createdBy)) {
            $this->_createdBy = UserFactory::loadUserById($this->_createdBy);
        }
        return $this->_createdBy;
    }
}

这个技巧只适用于方法。您可以使用 __get__set 来实现此逻辑,但是当您添加属性时,您最终会得到一个大而令人讨厌的 switch() 块:

public function __get($name)
{
    switch ($name) {
        case 'createdBy':
            // blah blah blah
        case 'createdDate':
            // more stuff
        // more case statements until you scream
    }
}

如果您只是如果想要避免或推迟编写 getter 和 setter,请使用 __call 魔术方法来捕获 getProperty()setProperty() 之后的方法调用> 命名约定。您可以将所有默认的获取/设置逻辑放入 __call 中,并且不再触及它:

abstract class Object
{
    public function __call($method, $args)
    {
        $key = '_' . strtolower(substr($method, 3, 1)) . substr($method, 4);
        $value = isset($args[0]) ? $args[0] : null;
        switch (substr($method, 0, 3)) {
            case 'get':
                if (property_exists($this, $key)) {
                    return $this->$key;
                }
                break;

            case 'set':
                if (property_exists($this, $key)) {
                    $this->$key = $value;
                    return $this;
                }
                break;

            case 'has':
                return property_exists($this, $key);
                break;
        }

        throw new Exception('Method "' . $method . '" does not exist and was not trapped in __call()');
    }
}

开发的角度来看,这种方法非常快,因为您只需扩展 Object 类即可,定义一些属性,然后你就可以开始比赛了:

class Foo extends Object
{
    protected $_bar = 12345;
}

$foo = new Foo();
echo $foo->getBar();  // outputs '12345'
$foo->setBar(67890);  // next call to getBar() returns 67890
$foo->getBaz();       // oops! 'baz' doesn't exist, exception for you

执行的角度来看,它很慢,因为魔法方法实在是太慢了,但你可以稍后通过定义显式的 getBar() 来缓解这种情况代码> 和setBar() 方法(因为 __call 仅在调用未定义的方法时调用)。但如果某个特定属性不经常被访问,也许您并不关心它有多慢。关键是,稍后添加特殊的 get/set 方法很容易,而代码的其余部分永远不会知道其中的区别。

我从 Magento 抄袭了这种方法,我发现它对开发人员非常友好。在调用不存在的属性的 get/set 时抛出异常可以帮助您避免由拼写错误引起的虚拟错误。将特定于属性的逻辑保留在其自己的 get/set 方法中可以使代码更易于维护。但是您不必在开始时编写所有访问器方法,您可以轻松返回并添加它们,而无需重构所有其他代码。

问题是,你想优化什么?开发时间还是代码速度?如果您想优化代码速度,请确保在围绕瓶颈构建代码之前知道瓶颈在哪里。过早的优化是万恶之源。

This would be a welcome change in my
design process as I see the need for
getters/setters going away, but what
other hurdles/benefits might occur in
doing this?

You lose the ability to implement special get/set logic on a particular property. For properties that are scalars (strings, integers, booleans) maybe this is no problem. But what if you have a property that is a lazy-loaded class instance?

class Document
{
    protected $_createdBy;

    public function getCreatedBy()
    {
        if (is_integer($this->_createdBy)) {
            $this->_createdBy = UserFactory::loadUserById($this->_createdBy);
        }
        return $this->_createdBy;
    }
}

That trick only works in a method. You could use __get and __set for this logic but as you add properties you end up with a big nasty switch() block:

public function __get($name)
{
    switch ($name) {
        case 'createdBy':
            // blah blah blah
        case 'createdDate':
            // more stuff
        // more case statements until you scream
    }
}

If you just want to avoid or put off writing getters and setters, use the __call magic method to trap method calls that follow the getProperty() and setProperty() naming convention. You can put all the default get/set logic in __call and never touch it again:

abstract class Object
{
    public function __call($method, $args)
    {
        $key = '_' . strtolower(substr($method, 3, 1)) . substr($method, 4);
        $value = isset($args[0]) ? $args[0] : null;
        switch (substr($method, 0, 3)) {
            case 'get':
                if (property_exists($this, $key)) {
                    return $this->$key;
                }
                break;

            case 'set':
                if (property_exists($this, $key)) {
                    $this->$key = $value;
                    return $this;
                }
                break;

            case 'has':
                return property_exists($this, $key);
                break;
        }

        throw new Exception('Method "' . $method . '" does not exist and was not trapped in __call()');
    }
}

This approach is very fast from a development standpoint because you can just extend the Object class, define some properties, and you're off to the races:

class Foo extends Object
{
    protected $_bar = 12345;
}

$foo = new Foo();
echo $foo->getBar();  // outputs '12345'
$foo->setBar(67890);  // next call to getBar() returns 67890
$foo->getBaz();       // oops! 'baz' doesn't exist, exception for you

It's slow from an execution standpoint because magic methods are damned slow, but you can mitigate that later by defining explicit getBar() and setBar() methods (because __call is only invoked when you calling a method that isn't defined). But if a particular property doesn't get accessed very often, maybe you don't care how slow it is. The point is, it's easy to add special get/set methods later on and the rest of your code never knows the difference.

I cribbed this approach from Magento and I find it to be very developer-friendly. Throwing an exception when calling the get/set for a property that doesn't exist helps you avoid phantom bugs caused by typos. Keeping property-specific logic in its own get/set methods makes code easier to maintain. But you don't have to write all the accessor methods at the start, you can easily go back and add them without refactoring all your other code.

The question is, what are you trying to optimize? Developer time or code speed? If you want to optimize code speed, make sure you know where your bottlenecks are before building your code around them. Premature optimization is the root of all evil.

烟雨扶苏 2024-11-18 13:26:10

这是某种微观优化。理论上,您可以稍后使用魔术方法(__get 和 __set)在名称获取/设置上添加逻辑,但实际上并不需要那么多。再说一遍,实际上,只有当您对其他所有内容进行优化时,这种性能改进才重要,即使是几微秒也会增加价值。在这种情况下,您可以使用其他优化技术,例如将所有包含的 PHP 文件合并为一个、删除类型提示、减少函数参数的数量、使用普通函数而不是类。但通常添加一个简单的缓存会比所有这些微优化带来 10-100 倍的性能提升。

This is some kind of micro-optimization. Theoretically, you can later add logic on name get/set by using magic methods (__get and __set) but practically it is not needed so much. And again, practically, this performance improvement only important only if you have everything else so optimized, that even a few microseconds add the value. In this case you can use other optimization techniques like merging all the included PHP files in one, remove type hints, decrease number of function parameters, use plain functions instead of classes. But usually adding a simple caching adds the 10-100x performance boost than all these micro-optimizations.

↘人皮目录ツ 2024-11-18 13:26:10

恐怕是一个样板答案,但我建议如下:
如果通过将此属性公开给其他用户来使您的类没有封装问题(强制执行业务逻辑等),那么这样做是完全可以的。

A boilerplate answer, I'm afraid but I would suggest the following:
If you have no encapsulation problems for your class (enforcing business logic, etc.) by exposing this property to other users, it is perfectly ok to do so.

☆獨立☆ 2024-11-18 13:26:10

您还可以使用 __get 和 __set 魔术方法:

class Example
{
    private $allowedProps = array('prop1', 'prop2', 'prop3');
    private $data = array();

    public function __set($propName, $propValue)
    {
        if (in_array($propName, $this->allowedProps))
        {
            $this->data[$propName] = $propValue;
        }
        else
        {
            // error
        }
    }

    public function __get($propName)
    {
        if (array_key_exists($propName, $this->data))
        {
            return $this->data[$propName];
        }
        else
        {
            // error
        }
    }
}

You could also use the __get and __set magic methods:

class Example
{
    private $allowedProps = array('prop1', 'prop2', 'prop3');
    private $data = array();

    public function __set($propName, $propValue)
    {
        if (in_array($propName, $this->allowedProps))
        {
            $this->data[$propName] = $propValue;
        }
        else
        {
            // error
        }
    }

    public function __get($propName)
    {
        if (array_key_exists($propName, $this->data))
        {
            return $this->data[$propName];
        }
        else
        {
            // error
        }
    }
}
箜明 2024-11-18 13:26:10

起初我很惊讶,我想……wtf。但在我思考了几秒钟后,我意识到这个示例在循环中调用了 getter 函数 100 万次。当然,如果变量被包装在 getter 中,我们添加了指令,当然会花费更长的时间。

在我看来,在大多数情况下,这是非常微不足道的,因为我还没有遇到过在运行时调用 getters 接近 100 万次的脚本。如果您确实需要将性能压缩到最后一点,那么了解这种优化技术是很有好处的。

At first I was surprised, I was like ... wtf. But after wrapping my brain on it a few seconds, I realized the example calls the getter function 1 million time in a loop. Of course if the variable is wrapped in a getter, we have added instructions and of course it's going to take longer.

In my opinion in most situations this is very trivial because I have yet to come accross a script that comes event close to calling getters 1 million time when running. If you do need to squeeze performance to the very last drop, it is good to know this optimisation technique.

∞梦里开花 2024-11-18 13:26:10

这取决于 $name 是否公开。如果不是,您将无法直接访问/修改它。权衡是将类的内部数据元素直接暴露给集成商。这对于某些类来说可能没问题,但对于其他类则不然。

例如,您不一定希望其他人能够直接在 Product 类中修改产品的价格。

It depends on whether $name is public or not. If it's not, you can't access/modify it directly. The trade off is exposing your class's internal data elements directly to integrators. This may be OK for some classes, but not for others.

For example, you wouldn't necessarily want others to be able to modify the price of a product directly in a Product class.

追星践月 2024-11-18 13:26:10

我想说这确实是个人喜好的问题。如果性能确实那么重要,那么我认为您已经回答了自己的问题。

但是,在第一个示例中,您仍然可以在没有 getter/setter 的情况下访问 dog::name ,就像在第二个示例中一样: $rover->name = 'rover'; 因为 $name 是公开的。

如果您特别想隐藏类成员,则需要声明变量 privateprotected,然后需要 getter/setter。

I would say this is really a matter of personal preference. If performance is truly that important, then I think you answered your own question.

However, in your first example, you can still access dog::name without the getter/setter just like you do in your second example: $rover->name = 'rover'; because $name is public.

If you specifically want to hide a class member, you would need to declare the variable private or protected and then a getter/setter would be necessary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文