Основными двумя причинами против использования статических методов являются:
- код с использованием статических методов трудно проверить
- код с использованием статических методов трудно расширить
Наличие статического вызова метода внутри какого-либо другого метода на самом деле хуже, чем импорт глобальной переменной. В 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 есть функции, и мы должны их использовать.