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

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

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

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

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

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

Ответ 1

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

Простейшим способом рассмотрения вещей может быть:

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

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

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

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

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

Ответ 2

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

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

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

Мишко Хевери, работающий в качестве 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();
    }
}

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

$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 имеет функции, и мы должны их использовать.

Ответ 3

Таким образом, в PHP static может применяться к функциям или переменным. Нестатические переменные привязаны к определенному экземпляру класса. Нестатические методы действуют на экземпляр класса. Итак, создайте класс под названием 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, которые действуют на отдельные сообщения, и вы пишете статические методы, которые действуют на коллекции сообщений.

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

Позвольте мне привести один пример. Поскольку каждый PHP script создает 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, если нет одной причины, почему это не должно.

Ответ 7

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

Хорошим примером может служить уровень абстракции 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'

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

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

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

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