Согласно руководству PHP , класс вроде этого:
abstract class Example {}
не может быть создан. Если мне нужен класс без экземпляра, например, для шаблона реестра:
class Registry {} // and later: echo Registry::$someValue;
считалось бы хорошим стилем просто объявить класс абстрактным? Если нет, то каковы преимущества скрытия конструктора в качестве защищенного метода по сравнению с абстрактным классом?
Обоснование для запроса: насколько я понимаю, это может привести к некоторому злоупотреблению функциями, поскольку в руководстве ссылки на абстрактные классы больше похожи на чертежи для последующих классов с возможностью создания экземпляров.
Обновление: Прежде всего, спасибо за все ответы! Но многие ответы звучат совершенно одинаково: «Вы не можете создать абстрактный класс, но для реестра, почему бы не использовать одноэлементный шаблон?»
К сожалению, это было более или менее точно повторение моего вопроса. В чем преимущество использования одноэлементного шаблона (он же скрывает __construct()
) по сравнению с просто объявлением его abstract
и не нужно беспокоиться об этом? (Как, например, это сильная коннотация между разработчиками, что abstract
классы фактически не используются или так).
Если ваш класс не предназначен для определения какого-либо супертипа, он не должен быть объявлен abstract
, я бы сказал.
В вашем случае я бы предпочел пойти с классом:
__construct
и __clone
как частные методы
Теперь зачем использовать Singleton, а не только статические методы? Я полагаю, что по крайней мере несколько причин могут быть действительными:
__construct
и __clone
private и добавить некоторый метод getInstance
. $this
, properties, … __callStatic
был введен только в PHP 5.3 __getStatic
, __setStatic
, … Говоря это, да, какой-то код выглядит так:
abstract class MyClass { protected static $data; public static function setA($a) { self::$data['a'] = $a; } public static function getA() { return self::$data['a']; } } MyClass::setA(20); var_dump(MyClass::getA());
Аabstract class MyClass { protected static $data; public static function setA($a) { self::$data['a'] = $a; } public static function getA() { return self::$data['a']; } } MyClass::setA(20); var_dump(MyClass::getA());
Будет работать … Но это не кажется вполне естественным … и это очень простой пример (см. То, что я сказал ранее с помощью Late Static Binding и магических методов) .
То, что вы описываете, разрешено языком PHP, но это не предполагаемое использование abstract
класса. Я бы не использовал static
методы abstract
класса.
Вот и обратная сторона этого: другой разработчик может расширить ваш абстрактный класс, а затем создать экземпляр объекта, чего вы хотите избежать. Пример:
class MyRegistry extends AbstractRegistry { } $reg = new MyRegistry();
Правда, вам нужно только беспокоиться об этом, если вы передаете свой абстрактный класс другому разработчику, который не будет соответствовать вашему предполагаемому использованию, но именно поэтому вы также сделаете класс одиночным. Неконвертируемый разработчик может переопределить частный конструктор:
class Registry { private function __construct() { } } class MyRegistry extends Registry { public function __construct() { } // change private to public }
Если вы сами использовали этот класс, вы просто не хотели бы создавать экземпляр класса. Тогда вам не понадобится ни один механизм для предотвращения этого. Поэтому, поскольку вы разрабатываете это для использования другими, вам необходимо каким-то образом предотвратить предотвращение этих людей вашим предполагаемым использованием.
Поэтому я предлагаю эти две возможные альтернативы:
Придерживайтесь шаблона singleton и убедитесь, что конструктор также является final
поэтому никто не может расширить ваш класс и изменить конструктор на не-частные:
class Registry { private final function __construct() { } }
Сделайте свой реестр поддержкой как статического, так и использования объектов:
class Registry { protected static $reg = null; public static function getInstance() { if (self::$reg === null) { self::$reg = new Registry(); } return self::$reg; } }
Затем вы можете вызвать Registry::getInstance()
статически, или вы можете вызвать new Registry()
если хотите экземпляр объекта.
Затем вы можете делать отличные вещи, например, хранить новый экземпляр реестра в своем глобальном реестре! 🙂
Я реализовал это как часть Zend Framework, в Zend_Registry
Как говорили другие ребята, вы не можете создать абстрактный класс. Вы можете использовать статические методы в своем классе, чтобы предотвратить создание экземпляров, но я не являюсь поклонником этого, если у меня нет надлежащей причины.
Я мог бы быть немного не по теме, но в вашем примере вы сказали, что хотите это для класса шаблонов реестра. По какой причине вы не хотите его создавать? Не лучше ли создать экземпляр реестра для каждого реестра, который вы хотите использовать?
Что-то вроде:
class Registry { private $_objects = array( ); public function set( $name, $object ) { $this->_objects[ $name ] = $object; } public function get( $name ) { return $this->_objects[ $name ]; } }
В этом случае я бы даже не использовал Singleton.
Установка класса для абстрактного, который определяет только статические свойства / методы, не будет иметь реального эффекта. Вы можете расширить класс, создать его экземпляр и вызвать на нем метод, и он изменит свойства статического класса. Очевидно, очень смущает.
Аннотация также вводит в заблуждение. Абстрактной является определение класса, который реализует некоторые функции, но требует большего поведения (добавленного через наследование) для правильной работы. Кроме того, это обычно функция, которая не должна использоваться со статикой вообще. Вы практически приглашаете программистов использовать это неправильно.
Короткий ответ: частный конструктор будет более выразительным и отказоустойчивым.
В OO есть шаблоны, которые являются общими и хорошо распознаются. Использование abstract
в нетрадиционном способе может вызвать путаницу (извините, мои некоторые примеры в Java вместо PHP):
abstract
модификатор для класса, чтобы предотвратить прямую инстанцирование, но разрешить вывод из класса final
модификатор класса и final
модификатор класса и getInstance()
), который возвращает единственный экземпляр или одно из ограниченного числа экземпляров abstract
действительно предназначен для обозначения «плана», как вы его называете, для наследования классов.
Реестры обычно следуют одноэлементному шаблону, что означает, что он будет создавать экземпляр себя в частной переменной. Определение его как abstract
может помешать этому работать.
Я бы не использовал абстрактный класс. Я использую что-то более похожее на синглтон с защищенным / частным конструктором, как вы предлагаете. Должно быть очень мало статических свойств, отличных от $instance
который является фактическим экземпляром реестра. Недавно я стал поклонником типичного шаблона Zend Frameworks, который выглядит примерно так:
class MyRegistry { protected static $instance = null; public function __construct($options = null) { } public static function setInstance(MyRegistry $instance) { self::$instance = $instance; } public static function getInstance() { if(null === self::$instance) { self::$instance = new self; } return self::$instance; } }
Таким образом, вы получаете синглтон, но вы можете ввести настроенный экземпляр для использования. Это удобно для целей тестирования и наследования.
Цель абстрактного класса – определить методы, которые являются 1) значимыми для других классов и 2) не имеют смысла, если не в контексте одного из этих классов.
Чтобы парафазировать некоторые из php-документов, представьте, что вы подключаетесь к базе данных. Подключение к базе данных не имеет особого смысла, если у вас нет конкретного типа базы данных для подключения. Однако подключение – это то, что вы хотите делать независимо от типа базы данных. Следовательно, соединение может быть определено в абстрактном классе базы данных и унаследовано и сделано осмысленным, скажем, классом MYSQL.
Из ваших требований это звучит так, будто вы не намерены это делать, а вместо этого просто требуете класс без экземпляра. Хотя вы можете использовать абстрактный класс для обеспечения такого поведения, это кажется мне взломанным, потому что оно злоупотребляет целью абстрактных классов. Если я столкнулся с абстрактным классом, я должен уметь разумно ожидать, что у него будут, например, некоторые абстрактные методы, но у вашего класса их не будет.
Поэтому одноэлемент кажется лучшим вариантом.
Однако, если причина, по которой вы хотите иметь класс без экземпляра, просто так, что вы можете назвать его где угодно, тогда почему у вас вообще есть класс? Почему бы просто не загружать каждую переменную в качестве глобальной, и тогда вы можете просто называть ее напрямую, а не через класс?
Я думаю, что лучший способ сделать это – создать экземпляр класса, а затем передать его с помощью инъекции зависимостей. Если вы слишком ленивы, чтобы сделать это (и достаточно справедливо, если вы есть! Его код, а не мой.), То не беспокойтесь о классе вообще.
ОБНОВЛЕНИЕ: Кажется, что вы столкнулись с двумя потребностями: необходимость быстро делать то, что нужно делать правильно. Если вы не заботитесь о переносе тонны глобальных переменных ради экономии времени, вы предпочтете использовать абстракцию над синглом, потому что это связано с меньшим набором текста. Выберите, какая потребность важнее для вас и придерживайтесь ее.
Правильный путь здесь определенно не использовать Singleton или абстрактный класс и вместо этого использовать инъекцию зависимости. Быстрый способ состоит в том, чтобы иметь тонну глобалов или абстрактный класс.
По моему мнению, класс без экземпляра – это то, что вы не должны использовать в программе OOP, потому что целая (и единственная) цель классов – служить чертежами для новых объектов. Единственное различие между Registry::$someValue
и $GLOBALS['Registry_someValue']
заключается в том, что первый выглядит «fancier», но ни один из способов не является объектно-ориентированным.
Итак, чтобы ответить на ваш вопрос, вам не нужен «singleton class», вам нужен объект singleton, опционально оснащенный фабричным методом:
class Registry { static $obj = null; protected function __construct() { ... } static function object() { return self::$obj ? self::$obj : self::$obj = new self; } } ... Registry::object()->someValue;
Ясно, что abstract
работа здесь не будет работать.
Я бы сказал, что это вопрос кодирования habbits. Когда вы думаете об абстрактном классе, вам обычно нужно подклассы для использования. Таким образом, декларирование абстрактности вашего класса противоречит интуиции.
Кроме того, это просто вопрос использования self :: $ somevar в ваших методах, если вы сделаете его абстрактным, а не $ this-> somevar, если вы реализуете его как singleton.