Вызов свойства переменной напрямую против getter / setters - OOP Design - PullRequest
16 голосов
/ 02 июня 2011

Я знаю, что это, вероятно, субъективно, но я прочитал эту страницу оптимизации от Google для PHP , и они предлагают использовать свойство переменной напрямую, без необходимости использования методов получения и установки.Понятно, что в этом я вижу прирост производительности, но действительно ли это хорошая практика проектирования, которой следует следовать?

Их пример с использованием метода получения / установки:

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;

Это было бы долгожданным изменением в моем процессе проектирования, так как я вижу необходимость ухода геттеров / сеттеров, но какие другие препятствия / преимущества могут возникнуть при этом?

Ответы [ 7 ]

12 голосов
/ 02 июня 2011

Это было бы желанным изменением в моем процессе проектирования, так как я вижу необходимость ухода геттеров / сеттеров, но какие другие препятствия / преимущества могут возникнуть при этом?

Выпотерять способность реализовывать специальную логику get / set для определенного свойства.Для свойств, которые являются скалярами (строки, целые числа, логические значения), возможно, это не проблема.Но что, если у вас есть свойство, которое является экземпляром класса с отложенной загрузкой?

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
    }
}

Если вы просто хотите избежать или отложить написание геттерови сеттеры используют магический метод __call для перехвата вызовов методов, которые следуют соглашениям об именах getProperty() и setProperty().Вы можете поместить всю логику get / set по умолчанию в __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()');
    }
}

Этот подход очень быстр с точки зрения development , потому что вы можете просто расширитьКласс объекта, определите некоторые свойства, и вы приступите к гонкам:

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 облегчает поддержку кода.Но вам не нужно писать все методы доступа с самого начала, вы можете легко вернуться и добавить их без рефакторинга всего вашего другого кода.

Вопрос в том, что вы пытаетесь оптимизировать?Время разработчика или скорость кода?Если вы хотите оптимизировать скорость кода, убедитесь, что вы знаете, где находятся ваши узкие места, прежде чем строить свой код вокруг них.Преждевременная оптимизация - корень всего зла.

6 голосов
/ 02 июня 2011

Это какая-то микрооптимизация.Теоретически, вы можете позже добавить логику в имя / получить имя, используя магические методы (__get и __set), но практически это не нужно.И опять же, практически, это улучшение производительности важно только в том случае, если все остальное оптимизировано так, что даже несколько микросекунд увеличивают стоимость.В этом случае вы можете использовать другие методы оптимизации, такие как объединение всех включенных PHP-файлов в один, удаление подсказок типа, уменьшение количества параметров функции, использование простых функций вместо классов.Но обычно добавление простого кэширования увеличивает производительность в 10-100 раз по сравнению со всеми этими микрооптимизациями.

4 голосов
/ 02 июня 2011

Боюсь, что это стандартный ответ, но я бы предложил следующее: Если у вас нет проблем с инкапсуляцией для вашего класса (применение бизнес-логики и т. Д.) Путем предоставления этого свойства другим пользователям, это вполне нормально.

1 голос
/ 02 июня 2011

Сначала я был удивлен, я был как ... WTF.Но, обдумав это несколько секунд, я понял, что пример вызывает функцию getter 1 миллион раз в цикле.Конечно, если переменная обернута в геттер, мы добавили инструкции, и, конечно, это займет больше времени.

По моему мнению, в большинстве ситуаций это очень тривиально, потому что мне еще предстоит наткнуться на сценарийнаступает событие, близкое к вызову геттеров 1 миллион раз при запуске.Если вам нужно нужно снизить производительность до последней капли, полезно знать эту технику оптимизации.

1 голос
/ 02 июня 2011

Вы также можете использовать магические методы __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
        }
    }
}
0 голосов
/ 02 июня 2011

Я бы сказал, что это действительно вопрос личных предпочтений. Если производительность действительно , что важно, то я думаю, что вы ответили на свой вопрос.

Однако в вашем первом примере вы все равно можете получить доступ к dog::name без метода получения / установки, как вы делаете во втором примере: $rover->name = 'rover';, потому что $name общедоступно.

Если вы специально хотите скрыть члена класса, вам нужно объявить переменную private или protected, и тогда потребуется метод получения / установки.

0 голосов
/ 02 июня 2011

Это зависит от того, является ли $name публичным или нет.Если это не так, вы не можете получить доступ / изменить его напрямую.Компромисс заключается в том, что внутренние элементы данных вашего класса предоставляются интеграторам.Это может быть хорошо для некоторых классов, но не для других.

Например, вы не обязательно хотите, чтобы другие могли изменять цену продукта непосредственно в классе Product.

...