|
Программирование
On-line приложения
Почитать
Web-сервер Apache
Печать и форматирование
MySQL
Разные рецепты
Сборка/установка
Редактор vi
Справки
Философия
Мой опыт
Скачать
Программы на Tcl/Tk (GUI)
Программы на Python/Tk (GUI)
Программы (CLI)
Help
Хобби
Фракталы
on-line
Язык для рисования фракталов
Гиперкуб
Теория относительности
Ампуллярии
Преподавание
Студенту/абитуриенту
Мой опыт
Автора!
|
Так программировать нельзяЕщё недавно я бы не стал бы писать это статью. Ошибки описанные в ней представлялись мне очевидными распространёнными, но вполне простительным. Но судьба дала мне шанс познакомиться с кодом (около 100.000 строк), полностью построенным по ошибочной схеме и я увидел, к чему может привести беспечность при выборе генеральных направлений. ПодходыРечь пойдёт о двух вариантах организации объектно-ориентированного кода. Оба подхода приводят к решению и оба встречаются в реальной жизни. Итак, выражаясь формально, нам надо получить из данных a данные b=f(a). (Собственно, всё программирование сводится к преобразованию одних данных в другие.) Подход — программирование на объектахМы можем создать объект-конвертер и пользоваться им. Пример на Perl: $c = new F; # создали объект-конвертер $a = $c->f($b); # конвертируем Естественно этот объект-конвертер можно использовать многократно, но может иметь некие настройки, которые можно менять по мере необходимости (хотя, хорошо ли это? возможно лучше все настройки делать при конструировании? во многом ответ зависит от конкретной ситуации). Код может выглядеть как-то так: $c = new F;
$c->set_mode(2); # включили режим 'два'
print $c->f('one'); # конвертируем 'one'
$c->set_mode(3); # включили режим 'три'
print $c->f('two'); # конвертируем 'two'Этот подход может показаться избыточным. Ну зачем нам создавать
лишнюю сущность ( Подход — программирование на конструкторахСтрого говоря, проблему можно решить с помощью статического метода: $b=F->f($a); но тогда не понятно, за чем вообще нужны объекты? Если мы остаёмся в рамках ОО-парадигмы, то есть ещё один способ: сделать всё конструктором: $b = new F($a); Обратите внимание: теперь мы имеем конструктор, но он возвращает
не объект; мы имеем класс По сути, программирование на статических методах очень похоже на программирование на конструкторах, но второе выводит нас на «новый уровень абстракции», с которого можно шагнуть дальше. На пример можно сделать (нужно ли? скоро увидим) конструктор,
который создаёт «пустой»
объект, а параметр $e = new F; # "пустой" объект $e->set($a); # будем обрабатывать $a $b = $e->result; # обработать Мы, казалось бы, возвращаемся к первому методу. Объект $e = new F;
print $e->set('one')->result;
print $e->set('two')->result;Такая схема может показаться изящной и ни чем не уступающей
первой схеме. Однако давайте попытаемся увидеть за
деревьями лес. Что делает, в данном случае, конструктор?
Ничего. Вся функциональность конструктора вынесена
в метод Сейчас же, давайте отдадим дань другим авторам. Примеры. ПримерыПримеров программирования на объекта очень много, но я хочу начать с программирования на конструкторах. (То есть со второй модели.) Программирование handler'ов в mod_perl (программирование на конструкторах)Типичный mod_perl handler выглядит так: sub handler($$) :method {
my ($class, $r) = @_;
$r->content_type('text/plain');
$r->print('Hello!');
return Apache2::Const::OK;
}Как видите, по организации, это конструктор. Однако, по сути
это статический метод, который обрабатывает объект Сервер вызывает этот метод как-то так: $status = handler HandlerClass ($r) Вам это не напоминает уже виденный фрагмент? Сравните с: $b = new F($a); Просто конструктор называется не DBI (промежуточное решение)Разработчики DBI предложили превосходное решение сложной (хотя и весьма распространённой) задачи. Им нужно было естественным образом навязать программисту последовательность действий connect-prepare-execute и они прекрасно справились с задачей, введя дополнительные классы. Анатомия этих действий выглядит так: # connect -- просто статический метод # $dbh -- объект класса DBI::db $dbh = DBI->connect(...) # $sth -- объект класса DBI::st $sth = $dbh->prepare(...); $rv = $sth->execute; Я не назвал Таким образом, пока вы не сделали Но в этой схеме есть трещинки. Чтобы получить данные вам снова нужен объект класса $e = new F; # "пустой" объект $e->set($a); # будем обрабатывать $a $b = $e->result; # обработать Есть и более мелкие трещинки в архитектуре DBI. На пример,
метод Однако, DBI спроектирован хорошо, хоть и не во всём следует единой логике. Он уверенно занимает промежуточную позицию, в нём есть и программирование на объектах, не позволяющее допускать ошибок, и программирование на конструкторах. SCGI (программирование на объектах)Примеров чистого программирования на объектах очень много. Объектно-ориентированное программирование для того и создавалось, чтобы программировать на объектах. Вы легко найдёте массу таких примеров в Perl, а здесь я хотел бы привести пример на Python и вот почему. SCGI — это аналог FastCGI. Идеология же очень близка и mod_perl'y (и mod_python'у, и любому mod_*). Приложение является сервером, оно стартует один раз, а потом обслуживает запросы. Программист под SCGI должен, точно так же как в mod_perl, написать свой обработчик запросов. Как это реализуется в mod_perl мы уже видели, давайте теперь посмотрим, как та же самая задача реализована в SCGI/Python. Приложение выглядит так: class MyAppHandler(scgi.scgi_server.SCGIHandler):
def produce(self, env, bodysize, input, output):
output.write('Сontent_type: text/plain\n\nHello!\n')
scgi.scgi_server.SCGIServer(handler_class=MyAppHandler,
host='127.0.0.1',
port=3000,
max_children=3).serve()Метод Строго говоря, мы получаем не один объект класса Обратите внимание, на сколько это похоже на наш код: $c = new F; # при запуске сервера $a = $c->f($b); # при каждом запросе Одна и та же задача в mod_perl и в Python/scgi.scgi_server решена совершенно по-разному. Что значат эти отличия для разработчика? Недостатки и достоинстваДавайте сравним два представленных подхода, стараясь двигаться от плохого к хорошему. Зачем вообще нужны конструкторы при программировании на конструкторах?При программировании на конструкторах, как вы уже видели, конструкторы в общем-то не нужны. Они являются статическими методами и чаще всего могут быть заменены простыми функциями без всякого ООП. ООП даёт только одно «преимущество» — возможность наследовать. Это позволяет переопределять и до-определять части конструктора, создавать классы-наследники и прочее. Но суть подхода всё равно остаётся не объектно-ориентированной. Кроме того, всегда остаётся возможность и соблазн вынести часть работы конструктора в «до-инициализатор». Причём, при активном использовании наследования этот соблазн возрастает. Что делает конструктор при «до-инициализации»?При последовательном проведении в жизнь идеи «до-инициализации» объектов, конструкторы быстро вырождаются в пустышки типа: sub new
{ my $this = shift; bless( {}, ref($this) || $this) }Это цитата из реальной жизни. Что делает этот конструктор? Что за объект он сконструировал? Как им пользоваться? Любопытно, что вместо вызова такого конструктора: $a = new SomeClass; Можно было бы создать объект «на месте»: $a = bless({}, 'SomeClass');Это чуть длиннее, но избавляет от необходимости создавать отдельный файл для класса. То есть этот конструктор на столько не функционален, что его можно просто выкинуть и не заметить этого. Но давайте не будем забывать про самый главный вопрос: как же пользоваться таким объектом? В каком порядке вызывать «до-инициализаторы»?Когда конструктор создаёт неполноценный объект — порождается самое большое зло. Работать с этим объектом можно только очень осторожно вызывая инициализаторы в строго определённом порядке. Появляются такие персонажи: XXX->new->lang( shift->_id )->template или my $i = YYY ->new ->select_offset (as_int( ($spg - 1) * $snm )) ->select_num (as_int($snm)) ->select_sort (as_sort_col($sst)) ->select_order (as_sort_ord($sso)); То, что «до-инициализаторы» требуют определённого порядка вызова — страшно само по себе. Но когда «до-инициализаторы» начинают вызываться в разных частях программы, код становится совершенно непостижимым. Последней каплей становятся конструкции: ZZZ->new->preinit->_handler($_[3])->postinit($_[2]->value)->_postinit2; Вопрос: можно ли переставить местами Конфигурировать объект каждый раз?Работа на конструкторах создаёт массу сложностей. Приведу только один пример. Допустим, при работе, ваш модуль использует некие конфигурационные параметры (это не редкость, не правда ли?). Если вы используете mod_perl-подход, то у вас нет объекта, который жил бы на протяжении всей жизни сервера и мог бы один раз при инициализации считать конфигурацию и хранить её. Вернее, вы можете создать такой объект самостоятельно в глобальном пространстве имён и использовать его. Но сама архитектура mod_perl не помогает вам это сделать. Кроме того, глобальные переменные это не очень хорошо. Программируя на объектах (как было показано в примере SCGI/Python), вы получаете естественное место, где можно хранить конфигурацию сервера — ваш рабочий объект. Считывать же конфигурацию можно в конструкторе этого объекта. (На самом деле SCGI/Python предлагает специальный виртуальный метод, который вызывается конструктором, и множество других полезных методов.) Не нужны никакие глобальные переменные, всё просто и естественно. Может быть всё же лучше программировать на объектах?Подытожу. Чем хорошо программировать на объектах, а не на конструкторах.
Хотя, конечно, есть ситуации, когда необходим объект-контейнер с методом «добавить в контейнер», это похоже на до-инициализацию. Строго говоря, чёткой грани между программированием на конструкторах и программированием на объектах нет. Однако программирование на конструкторах таит немалую опасность и увлекаться им не следует. |
|
|
|