Вызов свойства переменной напрямую по сравнению с геттерами/сеттерами - ООП-дизайн

Я знаю, что это, вероятно, субъективно, но я прочитал эту страницу оптимизации от 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;

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


person Phill Pafford    schedule 02.06.2011    source источник
comment
Имейте в виду, что это руководство несколько устарело. Для 5.3 наверное еще актуально, но производительность здесь действительно не в тему. Я хотел порекомендовать опять Берри о геттерах/сеттерах., но я заметил, что вы уже это знаете..   -  person mario    schedule 02.06.2011
comment
Статья, на которую вы ссылаетесь, была либо написана человеком, не знающим внутренностей PHP, либо действительно устарела. По крайней мере, некоторые советы там совершенно неверны. Жаль, что что-то подобное может носить имя Google.   -  person NikiC    schedule 02.06.2011
comment
@mario, да, мне очень нравится пост Берри на эту тему, он действительно пытается сократить ненужный код.   -  person Phill Pafford    schedule 02.06.2011
comment
@nikic понятно, что есть некоторые вещи, с которыми я сам не согласен на странице, не могли бы вы немного уточнить?   -  person Phill Pafford    schedule 02.06.2011
comment
@Phill Pafford: Автор (не знаю по какой причине) считает, что $x = ...; echo $x использует вдвое больше памяти, чем echo ...; напрямую.   -  person NikiC    schedule 02.06.2011
comment
@mario @Phill Pafford Спасибо, ребята, очень вам признательны! Не знал, что мой блог настолько популярен. :)   -  person Berry Langerak    schedule 22.06.2011
comment
Ссылка на Google теперь не работает   -  person henrywright    schedule 12.03.2015


Ответы (7)


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

person Martin    schedule 02.06.2011

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

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

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

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

person squirrel    schedule 02.06.2011
comment
Спасибо, хорошие моменты. Моя оптимизация заключается не в написании меньшего количества кода, а в улучшении практики. Если мне нужен сеттер/геттер, то это нормально, но если вы просто используете его для установки значения, возможно, может быть более эффективный способ его написания. Я не большой поклонник волшебного метода, пользовался им раньше. Я не пробовал волшебный метод для XMLRPC, так как вам нужно объявить docblock для всех общедоступных/защищенных функций, может ли волшебный метод сделать это? - person Phill Pafford; 02.06.2011
comment
Дело в том, что сделать свойства класса общедоступными — это просто еще один способ иметь логику получения/установки по умолчанию, но эта логика обрабатывается интерпретатором PHP полностью вне вашего контроля. И если вы пишете кучу кода, который ожидает общедоступные свойства, то вернуться позже и изменить его, чтобы использовать методы доступа, очень сложно. Что касается XMLRPC, я думаю, это зависит от инструмента, который вы используете для обработки вызовов XMLRPC. Не могли бы вы просто добавить докблоки без определений методов? - person squirrel; 02.06.2011

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

person Alex Netkachov    schedule 02.06.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
        }
    }
}
person Major Productions    schedule 02.06.2011
comment
С точки зрения производительности это на самом деле медленнее, чем использование геттеров и сеттеров. См. этот пост в блоге: erosbence.blogspot.com/2011/07/getters. -setters-performance.html - person chiborg; 14.07.2011
comment
Удар по производительности незначителен. Тем не менее, это все же лучший дизайн, чем просто оставлять вещи публичными. - person Major Productions; 14.07.2011

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

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

person stefgosselin    schedule 02.06.2011

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

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

person AJ.    schedule 02.06.2011
comment
Ну, я бы, вероятно, сделал его защищенным/приватным, если только не требуется сделать его общедоступным. - person Phill Pafford; 02.06.2011
comment
Если вы используете private, вы не можете получить доступ за пределами класса. Основные вещи ОО. Дело в том, что их рекомендуемая оптимизация применима только к общедоступным атрибутам. - person AJ.; 02.06.2011

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

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

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

person steveo225    schedule 02.06.2011