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

170

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

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

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

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

Это все просто стиль? Есть ли правильный способ сделать это?

user73119
источник

Ответы:

123

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

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

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

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

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

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

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

Паскаль МАРТИН
источник
Так в основном использовать объекты при работе с уникальными вещами, такими как фотография, пользователь, публикация и т. Д., И использовать статические, когда они предназначены для общих вещей?
Роберт Роча
1
хорошо говоря о пользователе, у вас есть только один активный пользователь на каждый запрос. так что иметь смысл иметь активного пользователя статического запроса имеет смысл
My1
69

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

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

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

У Мишко Хевери , который работает в 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();

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

Мишко Хевери также проводит четкое различие между синглетонами и синглетонами (с большой буквы или без нее). Разница очень простая. Синглтоны с нижним регистром "s" приводятся в действие проводкой в ​​index / main. Вы создаете экземпляр объекта класса, который не реализует шаблон 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) статические члены, - это когда я знаю, что соответствующее поле будет иметь одно и то же значение в перекрестном экземпляре. В этот момент вы можете использовать статическое свойство и, возможно, пару статических методов получения / установки. В любом случае, не забудьте добавить возможность переопределения статического члена элементом экземпляра. Например, Zend Framework использовал статическое свойство, чтобы указать имя класса адаптера БД, используемого в экземплярах Zend_Db_Table. Прошло некоторое время с тех пор, как я их использовал, поэтому, возможно, это уже не актуально, но вот как я это помню.

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

Ionuț G. Stan
источник
1
@ Понятно, я не сомневаюсь в этом. Я понимаю, что в должности / роли тоже есть ценность; однако, как и все, что связано с XP / Agile, у него есть очень простое имя.
Джейсон
2
«Инъекция зависимости», я считаю, является слишком сложным термином для этого. МНОГО и много читали об этом по всему так и Google-Land. Что касается «синглетонов», вот что действительно заставило меня задуматься: slideshare.net/go_oh/… Разговор о том, почему PHP синглтоны действительно не имеют никакого смысла. Надеюсь, что это немного способствует старому вопросу :)
dudewad
22

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

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

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

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
Этот ответ довольно хороший, особенно с обновлением, которое вы сделали, потому что по этой причине я остановился на этом вопросе. Я понимаю функциональность "::" против "$ this", но когда вы добавляете в учетную запись, приведенный вами пример постов, извлекаемых из БД в ассоциативном массиве. Он добавляет совершенно новое измерение, то есть также в основной тон этого вопроса.
Гербен Якобс
Это один из лучших практических (по сравнению с теоретическими) ответов на этот часто задаваемый вопрос по PHP, приложение очень полезно. Я думаю, что это ответ, который многие программисты PHP ищут, проголосовали!
Ajmedway
14

Это все просто стиль?

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

troelskn
источник
13

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

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

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

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

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

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

Все мои статические классы имеют функцию 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 все идет статично, если только нет одной причины, почему это не следует делать.

JG Estiot
источник
Можете ли вы написать пример следующего пункта «Части вашего кода не могут делить переменные класса с другими частями» #JG Estiot
alam
10

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

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

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

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

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

grantwparks
источник
2
«Чтобы предотвратить создание экземпляров класса, можно также объявить его абстрактным», - мне это очень нравится. Ранее я читал о людях, делающих __construct () частной функцией, но я думаю, что если мне когда-нибудь понадобится, я, скорее всего, буду использовать абстрактный
Kavi Siegel
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'

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

Я надеюсь, что это помогает, наряду с другими ответами, чтобы помочь прояснить ситуацию. Как примечание стороны, проверьте " сцепление " и " сцепление ". Они обрисовывают в общих чертах некоторые очень и очень хорошие практики, которые следует использовать при написании кода, которые применимы ко всем языкам программирования.

Tres
источник
6

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

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

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

Мухаммед Хасан Хан
источник
3

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

Майкл Аарон Сафян
источник
3

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

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

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

dudewad
источник