Основні дві причини проти використання статичних методів:
- код за допомогою статичних методів важко перевірити
- код за допомогою статичних методів важко розширити
Наявність статичного виклику методу всередині якогось іншого методу насправді гірше, ніж імпорт глобальної змінної. У PHP класи - це глобальні символи, тому кожен раз, коли ви викликаєте статичний метод, ви покладаєтесь на глобальний символ (назва класу). Це випадок, коли глобальне - це зло. У мене виникли проблеми з таким підходом з деяким компонентом Zend Framework. Існують класи, які використовують статичні виклики методів (фабрики) для побудови об'єктів. Мені було неможливо поставити інший завод до цього екземпляра, щоб повернути налаштований об’єкт. Вирішення цієї проблеми полягає лише у використанні екземплярів та встановлення методів та примусовому застосуванні одиночних клавіш тощо на початку програми.
Мішко Хевері , який працює 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). Різниця дуже проста. Синглтони з малим регістром "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) статичних членів - це коли я знаю, що відповідне поле матиме однакове значення перехресної інстанції. У цей момент ви можете використовувати статичну властивість і, можливо, пару методів статичного геттера / сеттера. У будь-якому випадку, не забудьте додати можливість переозброєння статичного члена з членом екземпляра. Наприклад, Zend Framework використовував статичну властивість, щоб вказати ім'я класу адаптерів БД, який використовується в екземплярах Zend_Db_Table
. Минуло деякий час, коли я їх використовував, тому це вже не може бути актуальним, але це я пам’ятаю.
Статичні методи, які не мають стосунку до статичних властивостей, повинні бути функціями. PHP має функції, і ми повинні їх використовувати.