Когда использовать статические и инстанцированные классы

PHP – это мой первый язык программирования. Я не могу полностью обернуть свою голову, когда использовать статические классы и объекты-экземпляры.

Я понимаю, что вы можете дублировать и клонировать объекты. Однако за все время, когда я использовал php, любой объект или функция всегда оказывался единственным значением return (array, string, int) или void.

Я понимаю понятия в книгах, как класс персонажей видеоигр. дублировать объект автомобиля и сделать новый красный , что все имеет смысл, но что не является его применением в php и веб-приложениях.

Простой пример. Блог. Какие объекты блога лучше всего использовать в качестве статических или созданных объектов? Класс DB? Почему бы просто не создать экземпляр объекта db в глобальной области? Почему бы не сделать каждый объект статическим? Как насчет производительности?

Все ли это просто стиль? Есть ли способ сделать это?

Это довольно интересный вопрос – и ответы могут быть интересными тоже ^^

Самый простой способ рассмотреть вещи может быть:

  • используйте инстанцированный класс, в котором каждый объект имеет свои данные (например, имя пользователя)
  • используйте статический класс, когда это просто инструмент, который работает с другими вещами (например, синтаксический преобразователь для BB-кода для HTML, у него нет жизни сам по себе)

(Да, я признаю, действительно действительно слишком упрощен …)

Одна вещь о статических методах / классах заключается в том, что они не облегчают модульное тестирование (по крайней мере, в PHP, но, вероятно, и на других языках).

Еще одна вещь о статических данных заключается в том, что в вашей программе существует только один экземпляр: если вы когда-нибудь задали значение MyClass :: $ myData, оно будет иметь это значение и только оно, где угодно – говоря о пользователе, вы могли бы иметь только одного пользователя – что не так уж и здорово, не так ли?

Что я могу сказать о системе блога? Не так много, я бы написал как статичный, на самом деле, я думаю; возможно, класс доступа DB, но, вероятно, нет, в конце ^^

Основными причинами против использования статических методов являются:

  • код с использованием статических методов трудно проверить
  • код с использованием статических методов трудно расширить

Наличие вызова статического метода внутри другого метода на самом деле хуже, чем импорт глобальной переменной. В PHP классы являются глобальными символами, поэтому каждый раз, когда вы вызываете статический метод, вы полагаетесь на глобальный символ (имя класса). Это случай, когда глобальное зло. У меня были проблемы с подобным подходом с некоторыми компонентами Zend Framework. Существуют классы, которые используют статические вызовы (фабрики) для создания объектов. Мне не удалось поставить другую фабрику в этот экземпляр, чтобы вернуть настроенный объект. Решение этой проблемы состоит в том, чтобы использовать только экземпляры и внедрять методы и применять синглтоны и тому подобное в начале программы.

Miško Hevery , который работает в качестве Agile Coach в Google, имеет интересную теорию или, скорее, советует, что мы должны отделить время создания объекта от времени, когда мы используем объект. Таким образом, жизненный цикл программы разделен на две части. Первая часть (например, метод main() ), которая заботится обо всех проводках объектов в вашем приложении и той части, которая выполняет фактическую работу.

Поэтому вместо того, чтобы иметь:

 class HttpClient { public function request() { return HttpResponse::build(); } } 

Мы должны сделать следующее:

 class HttpClient { private $httpResponseFactory; public function __construct($httpResponseFactory) { $this->httpResponseFactory = $httpResponseFactory; } public function request() { return $this->httpResponseFactory->build(); } } 

А затем, на странице index / main, мы бы сделали (это шаг проводки объекта или время создания графика экземпляров, которые будут использоваться программой):

 $httpResponseFactory = new HttpResponseFactory; $httpClient = new HttpClient($httpResponseFactory); $httpResponse = $httpClient->request(); 

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

Miško Hevery также проводит четкое различие между синглтонами и синглтонами (с или без капитала S). Разница очень проста. Синглтоны с нижним регистром «s» выполняются проводкой в ​​индексе / главной. Вы создаете экземпляр объекта класса, который не реализует шаблон Singleton, и следите за тем, чтобы вы передавали этот экземпляр только любому другому экземпляру, который ему нужен. С другой стороны, Синглтон с капиталом «S» является реализацией классического (анти) шаблона. В основном глобальная маскировка, которая мало использует в мире PHP. Я до сих пор не видел этого. Если вы хотите, чтобы одно соединение с БД использовалось всеми вашими классами, лучше сделать это следующим образом:

 $db = new DbConnection; $users = new UserCollection($db); $posts = new PostCollection($db); $comments = new CommentsCollection($db); 

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

 /** * An example of a test using PHPUnit. The point is to see how easy it is to * pass the UserCollection constructor an alternative implementation of * DbCollection. */ class UserCollection extends PHPUnit_Framework_TestCase { public function testGetAllComments() { $mockedMethods = array('query'); $dbMock = $this->getMock('DbConnection', $mockedMethods); $dbMock->expects($this->any()) ->method('query') ->will($this->returnValue(array('John', 'George'))); $userCollection = new UserCollection($dbMock); $allUsers = $userCollection->getAll(); $this->assertEquals(array('John', 'George'), $allUsers); } } 

Единственная ситуация, когда я буду использовать (и я использовал их для имитации объекта прототипа JavaScript в PHP 5.3), статические члены – это когда я знаю, что соответствующее поле будет иметь одно и то же значение cross-instance. В этот момент вы можете использовать статическое свойство и, возможно, пару статических методов getter / setter. В любом случае, не забудьте добавить возможность переопределения статического члена членом экземпляра. Например, Zend Framework использует статическое свойство, чтобы указать имя класса адаптера DB, используемого в экземплярах Zend_Db_Table . Прошло некоторое время с тех пор, как я использовал их, чтобы они больше не были релевантными, но я помню это.

Статические методы, не относящиеся к статическим свойствам, должны быть функциями. PHP имеет функции, и мы должны их использовать.

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

title будет нестационарным членом. Он содержит название этого блога. У нас также может быть метод под названием find_related() . Это не статично, потому что для этого требуется информация из определенного экземпляра класса post post.

Этот класс будет выглядеть примерно так:

 class blog_post { public $title; public $my_dao; public function find_related() { $this->my_dao->find_all_with_title_words($this->title); } } 

С другой стороны, используя статические функции, вы можете написать такой класс:

 class blog_post_helper { public static function find_related($blog_post) { // Do stuff. } } 

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

По сути, это вопрос об объектно-ориентированном дизайне. Ваши классы – существительные в вашей системе, а функции, действующие на них, – это глаголы. Статические функции являются процедурными. Вы передаете объект функций в качестве аргументов.


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

В сценарии OO вы пишете методы в своем классе BlogPost которые действуют на отдельные сообщения, и вы пишете статические методы, которые действуют на коллекции сообщений.

Все ли это просто стиль?

Да, да. Вы можете писать совершенно хорошие объектно-ориентированные программы, не используя статические элементы. На самом деле некоторые люди утверждают, что статические члены – это, прежде всего, примесь. Я бы предположил, что – как новичок в oop – вы пытаетесь избежать статических членов вместе. Это заставит вас писать в объектно-ориентированном, а не процедурном стиле.

«Наличие вызова статического метода внутри другого метода на самом деле хуже, чем импорт глобальной переменной». (определить «хуже») … и «Статические методы, которые не имеют статических свойств, должны быть функциями».

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

  • дать им все пространство имен – избегая любых конфликтов имен
  • держите их физически расположенными вместе, а не разбросаны по всему проекту – другие разработчики могут с легкостью найти то, что уже доступно, и вряд ли будут изобретать колесо
  • позвольте мне использовать класс consts вместо глобальных определений для любых магических значений

это всего лишь удобный способ обеспечить более высокую степень сцепления и более низкую связь.

И FWIW – нет такой вещи, по крайней мере в PHP5, как «статические классы»; методы и свойства могут быть статическими. Чтобы предотвратить создание экземпляра класса, можно также объявить его абстрактным.

У меня есть другой подход к большинству ответов здесь, особенно при использовании PHP. Я думаю, что все классы должны быть статическими, если у вас нет веских причин, почему бы и нет. Некоторые из причин «почему нет»:

  • Вам нужно несколько экземпляров класса
  • Ваш класс должен быть расширен
  • Части вашего кода не могут передавать переменные класса с любыми другими частями

Позвольте мне привести один пример. Поскольку каждый скрипт PHP создает HTML-код, в моей структуре есть класс html writer. Это гарантирует, что ни один другой класс не попытается написать HTML, поскольку это специализированная задача, которая должна быть сконцентрирована в одном классе.

Как правило, вы должны использовать класс html следующим образом:

 html::set_attribute('class','myclass'); html::tag('div'); $str=html::get_buffer(); 

Каждый раз, когда вызывается get_buffer (), он сбрасывает все, чтобы следующий класс использовал html-writer, начинающийся в известном состоянии.

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

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

Теперь я приведу вам пример использования статических классов. Мой класс формы управляет списком элементов формы, таких как текстовые входы, выпадающие списки и многое другое. Он обычно используется следующим образом:

 $form = new form(stuff here); $form->add(new text(stuff here)); $form->add(new submit(stuff here)); $form->render(); // Creates the entire form using the html class 

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

Большинство классов полезности, таких как преобразования / форматирование строк, являются хорошими кандидатами на статический класс. Мое правило прост: все статично в PHP, если нет одной причины, почему это не должно.

Сначала спросите себя, что будет представлять этот объект? Экземпляр объекта хорош для работы с отдельными наборами динамических данных.

Хорошим примером может служить уровень абстракции ORM или базы данных. У вас может быть несколько соединений с базами данных.

 $db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1)); $db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2)); 

Эти два соединения теперь могут работать независимо:

 $someRecordsFromDb1 = $db1->getRows($selectStatement); $someRecordsFromDb2 = $db2->getRows($selectStatement); 

Теперь в этом пакете / библиотеке могут быть другие классы, такие как Db_Row и т. Д., Чтобы представлять определенную строку, возвращаемую из инструкции SELECT. Если этот класс Db_Row был статическим классом, тогда предполагается, что у вас есть только одна строка данных в одной базе данных, и было бы невозможно сделать то, что мог бы сделать экземпляр объекта. С экземпляром теперь вы можете иметь неограниченное количество строк в неограниченном количестве таблиц в неограниченном количестве баз данных. Единственным ограничением является серверное оборудование;).

Например, если метод getRows объекта Db возвращает массив объектов Db_Row, вы можете теперь работать с каждой строкой независимо друг от друга:

 foreach ($someRecordsFromDb1 as $row) { // change some values $row->someFieldValue = 'I am the value for someFieldValue'; $row->anotherDbField = 1; // now save that record/row $row->save(); } foreach ($someRecordsFromDb2 as $row) { // delete a row $row->delete(); } 

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

В одной части вашего приложения:

 Session::set('someVar', 'toThisValue'); 

И в другой части:

 Session::get('someVar'); // returns 'toThisValue' 

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

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

Если ваш класс является статичным, это означает, что вы не можете передавать его объект другим классам (так как нет экземпляра), так что все ваши классы будут напрямую использовать этот статический класс, что означает, что ваш код теперь тесно связан с классом ,

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

И да, это только одна из многих других причин, о которых уже упоминалось.

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

Я хотел бы сказать, что определенно есть случай, когда мне нужны статические переменные – в приложениях с несколькими языками. У вас может быть класс, в котором вы передаете язык (например, $ _SESSION ['language']), и он, в свою очередь, обращается к другим классам, которые созданы так:

 Srings.php //The main class to access StringsENUS.php //English/US StringsESAR.php //Spanish/Argentina //...etc 

Использование Strings :: getString («somestring») – отличный способ отвлечь ваше использование языка от вашего приложения. Вы могли бы сделать это, как вам угодно, но в этом случае каждый файл строк имеет константы со строковыми значениями, к которым обращается класс Strings, работает очень хорошо.