Pull to refresh

Comments 48

Отличная статья и отличный перевод. Заплюсовал.
Однако, некоторые утверждения кажутся мне странными.

это не дает никаких преимуществ по сравнению с процедурным программированием

Если несколько функций можно как-то сгруппировать по тематике, то я их группирую в один статический класс:
<?php
class My_Math {
	public static function fibonachi($n) {
		return ...;
	}
	public static function getK($m, $n) {
		return ceil(($m / $n) * log(2));
	}
}

Преимущества от этого следующие:
  • Вместо одного огромного файла functions.php, функции раскиданы по нескольким файлам и их легко найти
  • Работает autoload. Класс Math загрузится только тогда когда мне понадобится


Статические свойства никогда не должны быть доступны снаружи

Почему бы и нет?
<?php
class My_Date {
	// Пусть в моей системе будет только один справочник со списком месяцев
	public static $months = array('Январь', 'Февраль', ...);
	// Сделал бы его константой класса, но константы не могут быть массивами :(
}


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

Как насчёт этого?
<?php
class My_Pager {
	// Каждый экземпляр должен знать свой порядковый номер, что бы генерить уникальные ссылки
	protected $_num = 0;

	// Но как же его посчитать автоматически?
	// А вот как:
	protected static $_cntPagers = 0;

	public function __construct()
	{
		$this->_num = self::$_cntPagers++;
	}
}


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

Конечно, здесь есть смысл. В примере из статьи никаких преимуществ нет.

Статические свойства никогда не должны быть доступны снаружи

Почему бы и нет?

Вам придется жестко привязаться к My_Date::$months. Это ни хорошо, ни плохо. Важно понимать что это за собой влечет.

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

Как насчёт этого?


Неплохой трюк. Но Вы же и философию из этого не делаете, верно?

Автор призывает использовать инструменты с пониманием и избегать крайностей.
Я только хотел сказать, что в статье показана одна сторона медали и практически не освещена другая. Я привёл конкретные примеры противоречащие утверждениям автора:
это не дает никаких преимуществ

даёт.

Вам придется жестко привязаться к

Я хочу, чтобы в моей системе был один справочник в одном месте => мне приходится привязываться к этому месту. Вы можете изобретать сложные механизмы, например, хранить справочник в отдельном файле или в БД или просто в атрибуте объекта, но зачем?

Но Вы же и философию из этого не делаете, верно?

Делаю. В некоторых случаях нельзя делать статические члены класса; в некоторых случаях это необходимо. Автор статьи плохо описал случаи в которых это необходимо. Я попытался дополнить.

P.S.> В качестве примера можете посмотреть в Zend Framework. Они не брезгуют использовать статические члены класса. Просто они грамотно их используют и это не создаёт лишних зависимостей и не мешает тестированию.
>Конечно, здесь есть смысл. В примере из статьи никаких преимуществ нет.

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

В итоге если так дальше рассуждать, то статья сводится к «вот в таких-то конкретных ситуациях нет никакого смысла так делать». Так и хочется ответить: «Спасибо, Кэп! А что скакзать-то хотел?»
UFO just landed and posted this here
Единственный серьезный аргумент в пользу статических методов в большом числе случаев.
UFO just landed and posted this here
Если строго следовать этой логике, то вообще все переменные должны быть закрытыми + куча геттеров.

И это правильно.

Но иногда, встречаются исключения из правил. Я показал такое исключение, которое ко всему прочему, оказалось статическим членом.

вы бы легко впилили туда перевод

Этого не надо делать ни в коем случае! В справочнике могут быть ключевые слова. Например, они могут фигурировать в БД в ENUM полях. Да и перепиливать базовые классы под проект — это не хорошо.

Если вдруг моё приложение станет мультиязычным, то переводом я буду заниматься на совсем другом уровне. Там где происходит вывод:

echo '<select>';
foreach (My_Date::$months as $month) {
   echo '<option>'. translate($month) .'</option>';
}
echo '</select>';

Это немного сложнее, но правильнее. Это MVC-принципы. Перевод относится именно к «V».
Например, потом вам понадобится сделать API к вашему сайту. А у вас все справочники локализованы и API будет постоянно выдавать месяца на разных языках. Как клиентское ПО будет с этим работать?
Как клиентское ПО будет с этим работать?

Серверное будет ему указывать локаль. :)
И, за одно, передавать все свои словари :)
Почему бы и нет?

<?php
class My_Date {
    // Пусть в моей системе будет только один справочник со списком месяцев
    public static $months = array('Январь', 'Февраль', ...);
    // Сделал бы его константой класса, но константы не могут быть массивами :(
}




Немного не по теме статьи, но в таких случаях, возможно, лучше использовать SplEnum, который позволяет нативно получить список всех констант класса в виде массива с помощью метода getConstList().

Пример из документации по ссылке как раз класс с месяцами.

// Из доков:
echo new Month(Month::June); // выведет: 6
// Тот же самый результат:
echo Month::June;

Получается, смысл только в валидации входных значений:
// Но мне проще написать:
if ( ! array_key_exists(13, My_Date::$months)) {
    echo 'Error!';
}
// чем писать это:
try {
    new Month(13);
} catch (UnexpectedValueException $uve) {
    echo $uve->getMessage();
}

Чем же SplEnum лучше? Мне кажется, чем проще, тем лучше. Простые нативные массивы лучше чем класс, который никак не упрощает работу.
Ну, запись типа if ($month == Month::June) является и более понятной, и более гибкой для модификации, и более защищенной от ошибок чем if ($month == 6) или if ($month == arrray_search("Июнь", Month::$months)) (кстати, эти два фрагмента не эквивалентны с вашим определением Month::$month, у вас нумерация месяцев начинается с нуля )
Отличный материал! Наокнец-то кто-то про это написал!
Да, принцип инверсии контроля очень полезен и, кроме того, справедлив в любых объектно-ориентированных языках. Очень жаль, что очень много программистов про него забывают :( после них приходится такое разгребать, что удивляешься, как это вообще можно было написать, и как готовая программа в принципе работает.
Так и хочется сказать: «Садись, пять!»
Спасибо за отличный перевод интересной статьи!
Новый оксюморон. «Загубить код на PHP».
Часто использую статику (в разумных пределах) в своих проектах, однако в своей статье, за пример её использования получил критику.

Но, критикующий так и не ответил на вопрос, чем плох код:
    static private  $userData = NULL;
 
    public function getById($uid) {
               if (self::$userData) return self::$userData;
               self::$userData = $this->locGetById($uid); 
               return self::$userData;
            }
 
    private function locGetById($uid) {
              // some code $retData = ...               
               return $retData;
            }


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

А может я в чем-то не прав?
Нельзя говорить, что код «плохой», не зная контекста. В статье делается акцент на том, что при вызове метода, нельзя быть уверенным в наличии соединения к БД. В Вашем примере я вижу простой кеш. У Вас разные экземпляры класса будут возвращать одинаковый результат, если это «ок», тогда — «ок».
это Ok, так как мы один раз запрашиваем данные из БД, а используем их 10 раз.
Зачем же нам множить 10 раз один и тот же запрос, если кеш можно сделать в коде.
У данного кода очевидная проблема: метод getById($uid) отработает корректно только один раз. Далее с каким $uid не вызывай, он будет возвращать значение статического свойства $userData.
Очевидно, чтобы он работал корректно, $userData нужно сделать массивом и класть данные конкретного юзера используя в качестве ключа его $uid. Ну и проверять наличие данных в массиве по ключу соответственно.

Всё вышеперечисленное разумеется никак не касается использования статических свойств, просто ошибка в алгоритме.
принципиально ты прав, и спасибо за замечание.
В данном проекте мы используем данные только одного Пользователя (это однопользовательская онлайн игра).
Если бы Проекту нужно было несколько разных пользователей… например «лента друзей», то несомненно сделал бы $userData массивом.
Описание кода отличается от реализации (да и имя не подходит). Лучше бы вынести передачу $uid в конструктор/еще_куда из getById (переименовав его в getByUid). Тогда заголовок метода получится соответствующим реализации.
Спасибо за перевод, но статья ужасная, а её автор посмотреть бы на фреймверки, и почитать про исключения.
Я уже представляю как в каждом контролере надо будет писать $db = new Database('localhost', 'user', 'password');
Спасибо за перевод, но статья ужасная, а её автор посмотреть бы на фреймверки, и почитать про исключения.

Чем Вам не понравилась статья? Что не так с исключениями? Причем тут фреймворки?

Я уже представляю как в каждом контролере надо будет писать $db = new Database('localhost', 'user', 'password');

С чего бы это?
Автор акцентирует внимания что при вызове $bar = Foo::bar(); он не может узнать где произошла ошибка, в этом ему могут помочь исключения и трейс.

Ну давайте представим что у нас есть контроллер с 2 методами
class Controller {
    
    public function foo(){
        return My_Class::foo();
    }
    
    public function bar(){
        return My_Class::bar();
    }
}

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


class Controller {
    
    public function foo(){
        $db_confog = include 'config/database.php';
        $db  = new Database($db_confog['host'], $db_confog['user'], $db_confog['password']);
        $my_class = new My_Class($db);
        return $my_class->foo();
    }
    
    public function bar(){
        $db_confog = include 'config/database.php';
        $db  = new Database($db_confog['host'], $db_confog['user'], $db_confog['password']);
        $my_class = new My_Class($db);
        return $my_class->bar();
    }
}

А теперь давайте подумаем какой код лучше.
Судя по замыслу автора это должно быть как то так

Мне кажется, что Вы совсем не поняли замысла автора.
Я вам буду очень признателен если вы на примере моего кода покажете что имел ввиду автор.
Я думаю автор имел ввиду это:
class Controller {

    private $my_class;

    public __construct(My_Class $myClass) {
        $this->my_class = $myClass;
    }
    
    public function foo(){
        return $this->my_class->foo();
    }
    
    public function bar(){;
        return $this->my_class->bar();
    }
}

Никто не говорит что инициализацию надо проводить каждый раз. А вам полезно было бы посмотреть на фреймворки а именно паттерн Dependancy Injection и его использование.
И в итоге ваш контролер будет работать только с 1 классом?
И чем Dependancy Injection поможет в данном случае?
Пример контролера в Laravel 4
class UserController extends BaseController {

    /**
     * Show the profile for the given user.
     */
    public function showProfile($id)
    {
        $user = User::find($id);

        return View::make('user.profile', array('user' => $user));
    }

}
</spoiler>
И в итоге ваш контролер будет работать только с 1 классом?

Как раз код со статическими методами сможет работать с одним классом, а обьект параметр может принадлежать любому классу наследнику My_Class.

И чем Dependency Injection поможет в данном случае?

Dependency Injection поможет избежать дублирования логики инциализации.

User::find($id);

Ваш пример не совсем корректен т.к. не достаточно контекста чтобы определить насколько данный код хорош или плох.
Могу высказать свое ИМХО по этому поводу.
class View — скорее всего это класс из ядра фреймворка и его изменение не планируется. соответственно статические метода для него вцелом допустимы.
class User — удобный и логичный метод по созданию обьектов. Однако скорее всего рано или поздно прийдется изменять логику обработки пользователей (например создать отдельный класс для анонимных пользователей или пользователей с особыми правами) в таком случае статический метод может помешать таким изменениям так что в данном случае он скорее не оправдан. Кроме того такой метод затруднит тестирование кода если мы захотим подложить в этот контроллер тестовых пользователей.
Typofix:
Это может привести {в => к} проблемам.
Очень предвзятая статья. Никаких преимуществ, кроме личного мнения не продемонстрировано.

1. Как раз в примере с ООП зависимостей становится гораздо больше: класс Foo должен знать о Database с самого начала, хотя там может быть всего один метод использующий Database. Получается, что ВСЕ методы класса Foo должны знать о Database.

2. Мы обязаны производить подключение к БД, хотя оно может нам вовсе не понадобиться.

3. Любой нормальный код (со статическими методами или без) умеет генерировать исключения при обнаружении ошибок, например, отсутствии подключения к БД. Причем гораздо логичнее бросать их при использовании БД, а не в начале программы (получается вся программа должна знать о БД).

4. Способов реализации примера множество, например, статический конструктор при помощи autoload.

Вообще, в данном случае статический класс служит контекстом создаваемым абстракцией более высокого уровня для Foo.
Так как встроенных средств разделения слоев абстракции в PHP нет, то статические классы и глобальные переменные практически единственные НОРМАЛЬНЫЕ способы их реализации. И их использование это не ошибка программистов — это недостаток языка, который приходится покрывать костылями.

А пример в статье про плюсы подхода для абстракции легко решается созданием нескольких статических классов БД.

ИмяПриложения\БД1
ИмяПриложения\БД2
1. Зависимостей ровно столько же, только они не статические, а динамические и передаются явно.
Если всего один метод хочет знать Database, можно передать его туда явно. Не обязательно в конструктор же:
public function getSomething(Database $db);

2. Не обязаны. Можно коннектиться лениво — это зависит от реализации Database
Вы просто видите лишь верхушку айсберга, а вся ситуация такова:

Функции и методы используют множество слов, которые не были переданы им явно — операторы, функции и т.д. Все эти литеры являются глобальными, а для функции они являются кубиками из которых она строится.
Например, PHP — это не просто язык с операторами, это множество кубиков: сессии, расширения, глобальные супермассивы, модули к http-серверам и т.д. А глобальные переменные и статические методы всего лишь способы добавить новые кубики для построения функция средствами самого языка.
Просто нужно понять, что в момент запуска скрипта PHP контекст УЖЕ создан ядром PHP и можно либо поверх него создавать собственную систему, абстрагируясь от встроенных возможностей (только зачем тогда вообще использовать PHP, как основную систему?????), либо спокойно относится к хранению состояния в статическом классе (ведь он и будет объектом с состоянием в данном случае, его время жизни ограниченно запросом).

Т.е. вопрос не в правильном использовании ООП-подхода, а грамотной проекции их в PHP.
Просто нужно понять, что в момент запуска скрипта PHP контекст УЖЕ создан ядром PHP и можно либо поверх него создавать собственную систему, абстрагируясь от встроенных возможностей (только зачем тогда вообще использовать PHP, как основную систему?????)

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

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

Т.е. вопрос не в правильном использовании ООП-подхода, а грамотной проекции их в PHP.

Не понимаю, чем в этом случае состоит грамотная проекция ООП на PHP и кто эту «грамотность» оценивает?
В 5.4 уже есть встроенные возможности по агрегации функционала в класс (те самые кубики). Если грамотно и в меру это использовать, статики действительно будет по минимуму.
Постойте, никто не предлагал абстрагироваться от возможностей самого языка и городить абстракции на встроенные возможности.

А это часто бывает полезно :)
Например, PHP — это не просто язык с операторами, это множество кубиков: сессии, расширения, глобальные супермассивы, модули к http-серверам и т.д.

Когда-то я тоже подобным образом рассуждал, но потом понял, что сессии и прочие суперглобалы нельзя (не в смысле «запрещено», а в смысле «не должно») использовать где-то кроме заведомо глобального кода (типа index.php). С тех пор вместо, утрируя,
if ($_SERVER["method"] == "POST")
  do_smth_with_post_superglobal();

я пишу
if ($_SERVER["method"] == "POST")
  do_smth_with_array($_POST);
, а то и
do_smth_with_two_arrays($_SERVER, $_POST);
Вам просто стал не нужен PHP, а вы этого не поняли.
Что значит «не нужен»? Он справляется со своими задачами (прошли те времена, когда я писал на нем почти всё, включая «шелл-скрипты», лишь редко используя Basic для GUI или C/C++ для экономии ресурсов) не хуже известных мне конкурентов. А если учесть, что для получения $_SERVER и $_POST мне не нужно делать вообще ничего (например не подключать модуль cgi), то и лучше. Ну и другие причины есть не расставаться с PHP — как минимум с ним проще зарабатывать на жизнь.
> Классо-ориентированное программирование — глупость. Учитесь использовать ООП.

ООП — глупость. Учитесь использовать функциональное программирование.
> ООП — глупость. Учитесь использовать функциональное программирование.

Функциональное программирование — глупость. Учитесь декларативному программированию.
пацаны дух дениса ритчи жив только в си, где руками управляют памятью, где пацаны живут с машинно-зависимыми интеджерами и используют небезопасные указатели. только си, только хардкор!
ООП и ФП прекрасно дополняют друг друга.
Действительно, классо-ориентированное программирование практически идентично процедурному. Но конкретно в PHP это оправдано наличием автозагрузки классов (spl_autoload_register, __autoload).

То есть, можно, к примеру, создать хелпер Arr для работы с массивами, и вызывать его статические функции в коде — при этом он будет автоматически загружаться при первом вызове. Опять же, при этом все функции обретают пространство имен Arr — такой плюс потерял актуальность с версии 5.3, где появились настоящие пространства имен, но все же.

А вот, например, в Пайтоне такой подход не имеет смысла, там лучше воспользоваться честным процедурно-ориентированным программированием и поместить функции в файл/модуль arr.py.
Sign up to leave a comment.

Articles