Вызов свойства переменной напрямую vs getter/seters - OOP Design

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

Их пример с использованием 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;

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

Ответы

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

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