Вот результаты вашего кода, представленные Zend Debugger с PHP 5.3.6 на моей машине с Win7:

Как видите, вызовы к вашим __get
методам намного (в 3-4 раза) медленнее, чем обычные вызовы. Мы по-прежнему имеем дело с менее чем 1 с для вызовов в общей сложности 50 000, так что это незначительно при использовании в небольших масштабах. Однако, если вы хотите построить весь код на основе магических методов, вам нужно профилировать конечное приложение, чтобы увидеть, все ли оно незначительно.
Так много для довольно неинтересного аспекта производительности. Теперь давайте посмотрим на то, что вы считаете «не так важно». Я подчеркиваю это, потому что на самом деле это гораздо важнее, чем аспект производительности.
Относительно ненужной добавленной сложности, которую вы пишете
это действительно не увеличивает сложность моего приложения
Конечно, это так. Вы можете легко определить это, посмотрев на глубину вложения вашего кода. Хороший код остается слева. Ваш if / switch / case / if имеет четыре уровня. Это означает, что существует больше возможных путей выполнения, и это приведет к более высокой цикломатической сложности , что означает, что труднее поддерживать и понимать.
Вот числа для вашего класса A (без обычного геттера. Вывод сокращен с PHPLoc ):
Lines of Code (LOC): 19
Cyclomatic Complexity / Lines of Code: 0.16
Average Method Length (NCLOC): 18
Cyclomatic Complexity / Number of Methods: 4.00
Значение 4,00 означает, что оно уже на грани умеренной сложности. Это число увеличивается на 2 для каждого дополнительного футляра, который вы кладете в коммутатор. Кроме того, он превратит ваш код в процедурный беспорядок, потому что вся логика находится внутри переключателя / корпуса, а не делит его на отдельные блоки, например, одиночные добытчики.
Геттер, даже ленивый, не должен быть в меру сложным. Рассмотрим тот же класс с простым старым PHP Getter:
class Foo
{
protected $bar;
public function getBar()
{
// Lazy Initialization
if ($this->bar === null) {
$this->bar = new Bar;
}
return $this->bar;
}
}
Запуск PHPLoc для этого даст вам гораздо лучшую цикломатическую сложность
Lines of Code (LOC): 11
Cyclomatic Complexity / Lines of Code: 0.09
Cyclomatic Complexity / Number of Methods: 2.00
И это останется равным 2 для каждого дополнительного простого старого получателя, которого вы добавляете.
Кроме того, примите во внимание, что когда вы хотите использовать подтипы вашего варианта, вам придется перегружать __get
и копировать и вставлять весь блок switch / case для внесения изменений, тогда как с простым старым Getter вы просто перегружаете Геттеры нужно поменять.
Да, добавление всех геттеров - это более трудоемкая работа, но это также намного проще и, в конечном итоге, приведет к более удобному для сопровождения коду, а также имеет преимущество в предоставлении вам явного API, что приводит нас к вашему другому утверждению
Я специально хочу, чтобы мой API был "изолированным"; Документация должна рассказать другим, как его использовать: P
Я не знаю, что вы подразумеваете под "изолированным", но если ваш API не может выразить то, что он делает, это плохой код. Если мне нужно прочитать вашу документацию, потому что ваш API не говорит мне, как я могу взаимодействовать с ней, глядя на нее, вы делаете это неправильно. Вы запутываете код. Объявление свойств в массиве вместо объявления их на уровне класса (где они принадлежат) заставляет вас писать для него документацию, что является дополнительной и излишней работой. Хороший код легко читается и самодокументируется. Подумайте о покупке книги Роберта Мартина «Чистый код».
С учетом сказанного, когда вы говорите
единственная причина - красота API;
тогда я говорю: тогда не используйте __get
, потому что это будет иметь обратный эффект. Это сделает API уродливым. Магия сложна и неочевидна, и это именно то, что приводит к этим моментам WTF:

Чтобы закончить сейчас:
Я не вижу никаких реальных недостатков, я думаю, это того стоит
Вы, надеюсь, увидите их сейчас. Это не стоит того.
Дополнительные подходы к отложенной загрузке см. В различных шаблонах отложенной загрузки из PoEAA Мартина Фаулера :
Существует четыре основных типа ленивых нагрузок. Ленивая инициализация использует специальное значение маркера (обычно нулевое), чтобы указать, что поле не загружено.Каждый доступ к полю проверяет поле на наличие значения маркера и, если он не загружен, загружает его. Виртуальный прокси - это объект с тем же интерфейсом, что и у реального объекта.При первом вызове одного из его методов он загружает реальный объект, а затем делегирует. Держатель значения - это объект с методом getValue.Клиенты вызывают getValue для получения реального объекта, первый вызов запускает загрузку.A ghost - это реальный объект без каких-либо данных.При первом вызове метода призрак загружает полные данные в свои поля.
Эти подходы несколько различаются и имеют различные компромиссы.Вы также можете использовать комбинированные подходы.Книга содержит полное обсуждение и примеры.