Коли використовувати статичні та інстанційні класи


170

PHP - моя перша мова програмування. Я не можу зовсім обернути голову, коли використовувати статичні класи проти об'єктів.

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

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

Простий приклад. Блог. Які об’єкти блогу найкраще реалізувати як статичні або інстанційні об’єкти? Клас БД? Чому б просто не інстанціювати об'єкт db у глобальному масштабі? Чому б не зробити кожен об’єкт статичним? А як щодо продуктивності?

Це все просто стиль? Чи є правильний спосіб зробити це?

Відповіді:


123

Це досить цікаве запитання - і відповіді можуть отримати цікавий теж ^^

Найпростішим способом розглянути речі може бути:

  • використовувати клас екземплярів, де кожен об’єкт має дані самостійно (наприклад, у користувача є ім'я)
  • використовуйте статичний клас, коли це просто інструмент, який працює над іншими речами (як, наприклад, конвертер синтаксису для BB-коду в HTML; він не має життя самостійно)

(Так, зізнаюся, насправді дуже спрощено ...)

Єдине, що стосується статичних методів / класів - це те, що вони не полегшують тестування одиниць (принаймні, на PHP, але, мабуть, і на інших мовах).

Інша річ щодо статичних даних полягає в тому, що у вашій програмі існує лише один екземпляр: якщо ви де-небудь встановите MyClass :: $ myData, воно матиме це значення, і лише воно, де-небудь - Якщо говорити про користувача, ви могли б мати лише одного користувача - що не так здорово, чи не так?

Що можна сказати для системи блогу? Я думаю, що насправді я би не написав як статичний; можливо, клас DB-доступу, але, мабуть, ні, врешті ^^


Отже, в основному використовуйте об'єкти при роботі з унікальними речами, такими як фотографія, користувач, повідомлення тощо, і використовуйте статичні, коли це призначено для загальних речей?
Роберт Роша

1
Добре кажучи про користувача, у вас є лише один активний користувач на кожен запит. тому ненавидити активного користувача статичного запиту було б сенсом
My1

69

Основні дві причини проти використання статичних методів:

  • код за допомогою статичних методів важко перевірити
  • код за допомогою статичних методів важко розширити

Наявність статичного виклику методу всередині якогось іншого методу насправді гірше, ніж імпорт глобальної змінної. У 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 має функції, і ми повинні їх використовувати.


1
@Ionut, я в цьому не сумніваюся. Я розумію, що в позиції / ролі також є цінність; однак, як і все, що стосується XP / Agile, воно має дійсно флеозивну назву.
Jason

2
Я вважаю, що це надмірно складний звуковий термін для цього. МНОГО та багато читаючого на ньому в усьому SO та 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 - ви намагаєтесь уникати статичних членів разом. Це змусить вас у напрямку писати в об'єктно-орієнтованому, а не в процедурному стилі.


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
khurshed alam

10

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

Це обидва досить гнучкі заяви. Якщо у мене є набір функцій, пов’язаних за тематикою, але дані екземплярів є абсолютно невідповідними, я б швидше визначив їх у класі, а не кожній з них у глобальному просторі імен. Я просто використовую механіку, доступну в PHP5 для

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

це взагалі зручний спосіб забезпечити більшу згуртованість та менші зв'язки.

І FWIW - такого принаймні в PHP5 немає, як "статичні класи"; методи та властивості можуть бути статичними. Щоб запобігти інстанціюванню класу, його можна також оголосити абстрактним.


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'

Оскільки одночасно за сеанс буде одночасно лише один користувач, немає сенсу створювати екземпляр для сесії.

Я сподіваюся, що це допомагає разом з іншими відповідями допомогти прояснити речі. В якості бічної примітки ознайомтеся з " згуртованістю " та " зчепленням ". Вони окреслюють деякі дуже і дуже добрі практики, які слід використовувати при написанні коду, які стосуються всіх мов програмування.


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, працює досить добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.