Статические классы в PHP через абстрактное ключевое слово?

Согласно руководству по PHP, такой класс:

abstract class Example {}

не может быть создан. Если мне нужен класс без экземпляра, например. для шаблона реестра:

class Registry {}
// and later:
echo Registry::$someValue;

будет ли хорошим стилем просто объявить класс абстрактным? Если нет, то каковы преимущества сокрытия конструктора как защищенного метода по сравнению с абстрактным классом?

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

Обновление: Прежде всего, спасибо за все ответы! Но многие ответы звучат одинаково: «Вы не можете создать экземпляр абстрактного класса, но для реестра, почему бы не использовать одноэлементный шаблон?»

К сожалению, это было более или менее точным повторением моего вопроса. В чем преимущество использования одноэлементного шаблона (также известного как сокрытие __construct()) по сравнению с простым объявлением abstract и отсутствием необходимости беспокоиться об этом? (Например, между разработчиками существует сильная коннотация, что abstract классы на самом деле не используются или что-то в этом роде.)


person Boldewyn    schedule 22.03.2010    source источник
comment
Можете ли вы подумать о моем посте, я думаю, что упомянул явное преимущество.   -  person elias    schedule 19.04.2010
comment
Спасибо за ответы на все вопросы! К сожалению, окончание баунти выпало на достаточно загруженное время, так что я не мог реагировать на ответы так, как они того заслуживали. В конце концов я принял ответ Паскаля, потому что он был первым и после его редактирования также казался наиболее полным и полным. Тем не менее, я прочитал все и многому научился.   -  person Boldewyn    schedule 21.04.2010
comment
Интересно, кто раздал все -1. Хочешь объяснить?   -  person Boldewyn    schedule 21.04.2010


Ответы (10)


Если ваш класс не предназначен для определения какого-либо супертипа, я бы сказал, что его не следует объявлять как abstract.

В вашем случае я бы предпочел пойти с классом:

  • That defines __construct and __clone as private methods
    • so the class cannot be instanciated from outside
  • And, this way, your class could create an instance of itself


Теперь зачем использовать Singleton, а не только статические методы? Я полагаю, что, по крайней мере, пара причин может быть уважительной:

  • Использование синглтона означает использование экземпляра класса; упрощает преобразование неодноэлементного класса в одноэлементный: нужно только сделать __construct и __clone закрытыми и добавить некоторый метод getInstance.
  • Использование синглтона также означает, что у вас есть доступ ко всему, что вы можете использовать с обычным экземпляром: $this, свойства,...
  • Oh, a third one (not sure about that, but might have its importance) : with PHP < 5.3, you have less possibilities with static methods/data :
  • Поздняя статическая привязка была добавлена ​​только с PHP 5,3; и его отсутствие часто усложняет работу со статическими методами/классами; особенно при использовании наследования.


При этом да, вот такой код:

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());

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

person Pascal MARTIN    schedule 22.03.2010
comment
Значит, это просто стиль? - person Boldewyn; 23.03.2010

То, что вы описываете, разрешено языком 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
}

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

Поэтому я предлагаю эти две возможные альтернативы:

  1. Придерживайтесь одноэлементного шаблона и убедитесь, что конструктор также final, чтобы никто не мог расширить ваш класс и изменить конструктор на незакрытый:

    class Registry
    {
      private final function __construct() {
      }
    }
    
  2. Сделайте так, чтобы ваш реестр поддерживал как статическое, так и объектное использование:

    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.

person Bill Karwin    schedule 17.04.2010

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

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

Что-то вроде:

class Registry {
    private $_objects = array( );

    public function set( $name, $object ) {
        $this->_objects[ $name ] = $object;
    }

    public function get( $name ) {
        return $this->_objects[ $name ];
    }
}

Я бы даже не использовал Singleton в этом случае.

person Ondrej Slinták    schedule 22.03.2010
comment
Причина не создавать экземпляр: лень. Если бы я создал его экземпляр, мне пришлось бы объявить экземпляр как глобальную переменную в любой функции. Если я просто использую методы/члены статического класса, то класс реестра сам по себе является глобальным. - person Boldewyn; 23.03.2010
comment
Глобальные переменные - зло! Вместо этого попробуйте использовать другие параметры, например шаблон стратегии: en.wikipedia.org/wiki/Strategy_pattern - person Ondrej Slinták; 23.03.2010
comment
@Boldewyn: я должен согласиться с Ондреем, глобальные переменные — это зло, и их следует по возможности избегать (и да, это относится и к Синглтону, который я также предложил ниже). Есть способы избежать необходимости создания экземпляра реестра в каждом классе. Вдобавок к тому, что предложил Ондрей, альтернативой является создание экземпляра только один раз, а затем использование внедрения зависимостей для внедрения его в любые классы, которые должны его использовать. - person MicE; 28.03.2010
comment
Хорошо, я имел в виду другое. Я имею в виду, что с простым классом я могу сделать Registry::get('abc') сразу в любой функции, тогда как с синглтоном я должен сначала как-то захватить его, будь то глобальная переменная или через какой-то Registry::get_instance() перед доступом к нему. - person Boldewyn; 28.03.2010

Установка абстрактного класса, который определяет только статические свойства/методы, не будет иметь реального эффекта. Вы можете расширить класс, создать его экземпляр и вызвать для него метод, и это изменит свойства статического класса. Очевидно, очень запутанно.

Аннотация также вводит в заблуждение. Abstract предназначен для определения класса, который реализует некоторую функциональность, но требует большего поведения (добавленного через наследование) для правильной работы. Кроме того, обычно это функция, которую вообще не следует использовать со статическими данными. Вы практически предлагаете программистам использовать его неправильно.

Краткий ответ: частный конструктор был бы более выразительным и безопасным.

person elias    schedule 17.04.2010

В объектно-ориентированном подходе есть общие и хорошо известные шаблоны. Использование abstract нетрадиционным способом может вызвать путаницу (извините, некоторые мои примеры написаны на Java, а не на PHP):

  • abstract class - a class meant to conceptualize a common ancestor, but of which actual instances are not meant to exist (e.g. shape is an abstract superclass for rectangle and triangle).
    Commonly implemented by:
    • use abstract modifier on class to prevent direct instantiation, but allow deriving from the class
  • utility class - a class that does not represent an object in the solution space, but rather is a collection of useful static operations/methods, e.g. Math class in Java.
    Commonly implemented by:
    • make class non-derivable, e.g. Java uses the final modifier on class, and
    • предотвратить прямое создание экземпляров - не предоставлять конструкторы и скрывать или отключать любые неявные конструкторы или конструкторы по умолчанию (и конструкторы копирования)
  • singleton class - a class that does represent an object in the solution space, but whose instantiation is controlled or limited, often to insure there is only one instance.
    Commonly implemented by:
    • make class non-derivable, e.g. Java uses the final modifier on class, and
    • предотвратить прямое создание экземпляров - не предоставлять конструкторы и скрывать или отключать любые неявные конструкторы или конструкторы по умолчанию (и конструкторы копирования), и
    • предоставить конкретные средства для получения экземпляра - статический метод (часто getInstance()), который возвращает единственный экземпляр или один из ограниченного числа экземпляров
person Bert F    schedule 20.04.2010

abstract на самом деле предназначен для обозначения "чертежа", как вы его называете, для наследования классов.

Реестры обычно следуют одноэлементному шаблону, что означает, что они будут создавать экземпляры в частной переменной. Определение его как abstract помешало бы этому работать.

person keithjgrant    schedule 22.03.2010

Я бы не использовал абстрактный класс. Я бы использовал что-то более похожее на синглтон с защищенным/приватным конструктором, как вы предлагаете. Должно быть очень мало статических свойств, кроме $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;
  }
}

Таким образом, вы получаете синглтон, но вы можете внедрить сконфигурированный экземпляр для использования. Это удобно для целей тестирования и наследования.

person prodigitalson    schedule 22.03.2010
comment
Вы также должны добавить к нему private function __clone(){}, чтобы его нельзя было клонировать. - person SeanJA; 20.04.2010

Цель абстрактного класса состоит в том, чтобы определить методы, которые 1) имеют смысл для других классов и 2) не имеют смысла, если не в контексте одного из этих классов.

Чтобы перефразировать некоторые документы php, представьте, что вы подключаетесь к базе данных. Подключение к базе данных не имеет особого смысла, если у вас нет базы данных определенного типа для подключения. Тем не менее, подключение — это то, что вам нужно сделать независимо от типа базы данных. Таким образом, подключение может быть определено в абстрактном классе базы данных, унаследовано и осмысленно, скажем, классом MYSQL.

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

Поэтому синглтон кажется лучшим вариантом.

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

Я думаю, что лучший способ сделать это — создать экземпляр класса, а затем передать его с помощью внедрения зависимостей. Если вам лень это делать (и если честно! Это ваш код, а не мой), то вообще не заморачивайтесь с классом.

ОБНОВЛЕНИЕ: кажется, что у вас конфликт между двумя потребностями: потребностью делать что-то быстро и потребностью делать все правильно. Если вы не заботитесь о переносе тонны глобальных переменных ради экономии времени, вы, вероятно, предпочтете использовать абстракцию, а не синглтон, потому что это требует меньшего набора текста. Выберите, какая потребность для вас важнее, и придерживайтесь ее.

Правильный способ здесь определенно не использовать Singleton или абстрактный класс, а вместо этого использовать внедрение зависимостей. Быстрый способ — иметь кучу глобальных переменных или абстрактный класс.

person Rupert Madden-Abbott    schedule 14.04.2010

Насколько я понимаю, класс без экземпляра — это то, что вы не должны использовать в программе ООП, потому что вся (и единственная) цель классов — служить чертежами для новых объектов. Единственная разница между Registry::$someValue и $GLOBALS['Registry_someValue'] состоит в том, что первый выглядит "красивее", но ни один из них не является объектно-ориентированным.

Итак, чтобы ответить на ваш вопрос, вам не нужен «одноэлементный класс», вам нужен одноэлементный объект, необязательно оснащенный фабричным методом:

class Registry
{
    static $obj = null;

    protected function __construct() {
        ...
    }

    static function object() {
        return self::$obj ? self::$obj : self::$obj = new self;
    }
}

...

Registry::object()->someValue;

Очевидно, что abstract здесь не сработает.

person user187291    schedule 19.04.2010
comment
В PHP класс без экземпляра может быть весьма полезен. Я использую их как своего рода пространство имен; их можно загружать автоматически, их не нужно объявлять глобальными для использования внутри функции, а код легче читать, если вы выберете хорошее имя для класса. Он успешно порадовал меня очень простой реализацией. Хорошо это или нет / должны ли мы их использовать или нет? Мне все равно, потому что он чертовски прост в использовании, понятен и очень мощен. - person Savageman; 20.04.2010

Я бы сказал, что это вопрос привычки кодирования. Когда вы думаете об абстрактном классе, обычно вам нужно создать подкласс, чтобы использовать его. Таким образом, объявление вашего абстрактного класса противоречит здравому смыслу.

Помимо этого, это просто вопрос использования self::$somevar в ваших методах, если вы сделаете его абстрактным, а не $this->somevar, если вы реализуете его как синглтон.

person Decko    schedule 20.04.2010