Разумно ли иметь достаточное количество общедоступных свойств в классе?

Или, более конкретно, можно ли не полагаться на сеттеры и геттеры?

Я имею дело с классом, который проверяет наличие комнат и устанавливает публичные свойства, которых более десятка. Такие вещи, как:

  • unitNumber
  • Типы комнат (массив)
  • codeКорпоративный
  • codeGroup
  • номерДетский
  • номерВзрослые
  • номерКомнаты
  • валюта
  • минрейт
  • максимальная скорость
  • мылосервер
  • единицы ( массив )
  • идентификатор отеля

И после создания экземпляра объекта эти свойства устанавливаются с помощью $this-> внутри различных методов. Однако код, который имеет дело с объектом, часто устанавливает общедоступные свойства напрямую, а не с помощью методов получения/установки:

$object->something = 3;

foreach ($object->things as $thing ) { }

Если у меня будет время реорганизовать этот класс..

  • Должен ли я поместить все эти свойства в массив данных, который является частным свойством, и определить методы __set и __get?
  • Должен ли я сделать один метод получения для каждого из свойств?

person meder omuraliev    schedule 08.02.2010    source источник


Ответы (5)


На мой взгляд, редко бывает хорошей идеей иметь каких-либо публичных членов. Это увеличивает связь между классами и делает рефакторинг очень сложным (если он вам понадобится).

Сеттеры/геттеры - это то, что нужно, и очень небольшое снижение производительности, которое вы платите за это, обычно либо оптимизируется, либо превосходит элегантность.

Чтобы ответить на ваш вопрос о массиве и одиночном геттере на переменную, это вопрос вкуса. Я склонен хранить в массиве только переменные одного типа и отделять остальные.

person An Onion That is Red    schedule 08.02.2010
comment
Итак, как бы вы лично определили метод получения? Вы бы использовали __get? - person meder omuraliev; 09.02.2010

Лично мне еще предстоит найти действительно вескую причину для общественной собственности, хотя я открыт для предложений :-)

Хотя я предпочитаю определенные геттеры/сеттеры для каждого свойства (будь то прокси для обобщенного get($name) или нет). Я предполагаю, что у вас уже есть другой код, который использует прямое назначение, поэтому в этом случае я бы посоветовал продолжить использование магических методов __get/__set.

person prodigitalson    schedule 08.02.2010

Я думаю, что большинство людей порекомендуют использовать сеттеры и геттеры. Прямо сейчас вы ограничены простой установкой и получением свойства, но что, если вы хотите регистрировать доступ к этому свойству? Или, возможно, вы хотите сначала запустить значение с помощью функции проверки (электронная почта, номер телефона, почтовый индекс и т. д.). Возможно, вам потребуется вызвать другую функцию или установить другое свойство. Думаю, вы понимаете, к чему я клоню. Используя сеттеры и геттеры, вы добавляете ценный уровень инкапсуляции в свои классы, и в 99% случаев это стоит дополнительного набора текста, который вам понадобится;) Представьте, что вы пытаетесь выполнить приведенные выше примеры без сеттеров и геттеров. Мягко говоря, это была бы большая головная боль.

Редактировать: я забыл упомянуть Doctrine. Это средство сопоставления объектных отношений (ORM), которое может автоматически устанавливать для вас сеттеры и геттеры (среди прочего). Вы можете ознакомиться с ним на странице http://www.doctrine-project.org/.

person Steven Mercatante    schedule 08.02.2010
comment
На самом деле в последний раз, когда я проверял, Doctrine (1.2) не могла генерировать сеттеры/геттеры для вас из-за неспособности правильно определить определенные типы возможностей отношений. Лучшее, что он мог сделать, это настроить для вас магические методы. Тем не менее, каким-то образом плагин Symfony Doctrine надежно добавляет эти геттеры/сеттеры, но я предполагаю, что есть что-то еще, что они добавили в свой плагин, чтобы сделать необходимые предположения для этого действительными (возможно, исключая определенные типы функций псевдонимов). - person prodigitalson; 09.02.2010
comment
Спасибо за информацию, prodigitalson. Я использую Symfony + Doctrine, так что это объясняет, почему я не столкнулся с этой проблемой. Я надеюсь, что автономный пакет Doctrine снова будет поддерживать его в будущем. - person Steven Mercatante; 09.02.2010

Я бы сделал шаг назад и задал бы несколько более общих вопросов:

  • Почему я должен раскрывать так много информации; что его использует и почему?
  • Является ли этот класс просто структурой данных без поведения, и в этом случае он должен быть частным классом для какого-то другого класса?
  • Служит ли этот класс какой-то одной цели или он находится на пути к тому, чтобы стать монолитным?

Возможно, вы обнаружите, что можете создавать представления экземпляра класса для экспорта в базу данных, отображения в форме и т. д. Для начала ознакомьтесь с шаблонами "Строитель" и "Ациклический посетитель".

Что касается средств доступа, я не вижу необходимости использовать их для того, что вы описываете: для получения свойств класса и информации о внутреннем состоянии, также известной как структура. Однако для атрибутов класса я мог видеть преимущество в некоторых случаях, но больше для извлечения атрибутов, а не для мутаций состояния вашего объекта.

person bn.    schedule 08.02.2010

Если я могу добавить свою соль несколько месяцев спустя:

Это очень не-OO иметь публичные свойства. Все должно быть инкапсулировано просто потому, что (помимо других причин) использование прямого манипулирования атрибутами не дает вам способов легкого рефакторинга или выполнения (дополнительных) контрольных проверок, когда какой-либо внешний источник изменяет поле. Например, предположим, что у вас есть класс с множеством полей, который используется в проекте несколько раз, и этот проект содержит несколько тысяч файлов; это проект, который работает и расширяется уже несколько лет. Допустим, компания меняет свою бизнес-модель или обнаружена проблема с некоторым типом данных поля, и теперь требуется некоторая проверка; будете ли вы дублировать эту проверку во всех тех тысячах исходного кода, который напрямую обращается к общедоступному члену? В PHP решение может быть простым, но не в большинстве языков программирования OO (например, Java). Дело в том, что ООП основан на инкапсуляции. Короче говоря, инкапсуляция не Создавать не только чистый код, но и очень удобный в сопровождении (не говоря уже о рентабельности и связности) код.

Ваше предложение иметь закрытый член (массив), управляемый __get/__set, хорошо. Таким образом, если вам нужна дополнительная проверка по дороге, просто создайте свой сеттер и/или ваш геттер, и это будет конец. Некоторые могут возразить, что это контрпродуктивно, поскольку автозавершение кода не может срабатывать при __get / __set. ИМХО, полагаться на завершение кода - это просто ленивое кодирование. Но опять же, наличие у каждого члена собственного геттера и/или сеттера позволяет вам написать более полную документацию по API. Лично я обычно использую этот метод для внутренних занятий или занятий общего назначения. Если все ваши поля не требуют никакой валидации, или их, как вы сказали, несколько десятков, то использование магических методов, на мой взгляд, допустимо.

Суть в том, чтобы избежать прямого доступа членов к экземплярам класса, и точка. Как вы решите достичь этого, зависит исключительно от вас. Просто убедитесь, что API хорошо документирован, чем более абстрактным вы его сделаете.

И наконец, в PHP, если у вас уже есть используемые классы, которые не инкапсулируют свои поля, например, что-то вроде

class SomeObject {
   public $foo;
   public $bar;
   public $baz;
   //...
}

вы можете просто исправить этот класс без необходимости рефакторинга чего-то вроде:

class SomeObject {
   private $_foo;   // having underscore as prefix helps to know what's private/protected
   private $_bar;   // inside the code.
   private $_baz;

   public function __get($name) {
      $methodName = 'get'.ucfirst($name);
      if (method_exists($this, $methodName)) {
         return $this->{$methodName}();
      } else {
         throw new Exception("Method '{$methodName}' does not exist");
      }
   }

   public function __set($name, $value) {
      $methodName = 'set'.ucfirst($name);
      if (method_exists($this, $methodName)) {
         $this->{$methodName}($value);
      } else {
         throw new Exception("Method '{$methodName}' does not exist");
      }
   }

   public function getFoo() { return $this->_foo; }
   public function setFoo($value) { $this->_foo = $value; }

   public function getBar() { return $this->_bar; }
   public function setBar($value) { $this->_bar = $value; }

   public function getBaz() { return $this->_baz; }
   public function setBaz($value) { $this->_baz = $value; }

}

А потом

$obj = new SomeObject();
$obj->foo = 'Hello world';    // legacy code support
$obj->setFoo('Hello world');  // same thing, but preferred

И вы удовлетворяете как парадигме OO, так и имеете прямой доступ к атрибутам экземпляра. Вы также можете иметь __call() проверку префикса 'get' или 'set' и вызывать __get() и __set() соответственно, но я бы не стал заходить так далеко, хотя это действительно позволило бы классам общего назначения получать доступ к своим закрытым членам через ->member и ->getMember()/->setMember()

person Yanick Rochon    schedule 23.08.2010