Несколько лет назад я читал книгу "Совершенный код" и выразил свое несогласие с автором, когда он говорил, что нужно писать не на языке, а с использованием языка. Как пример автор приводил утверждение, что если в языке нет объектов, то их нужно изобрести и написать. Это слишком радикальный метод, который я не могу поддержать. В данном случае проще и эффективнее будет поменять язык на объектный. Такие языки, как Delphi и Java слишком разные, чтобы писать на них одинаково, потому что у языков слишком разная идеология.
В последнее время я начинаю думать, что лучше бы все послушали Макконелла и всегда программировали с использованием языка, особенно программисты Delphi и С#. Почему? Потому что это визуальные языки и в них среды разработки очень мощные и балуют программиста простотой. Достаточно кликнуть мышкой, чтобы создать обработчик события и написать в нем код логики. Для очень и очень маленьких приложений это терпимо, а вот для больших проектов - это зло.
Существует множество подходов к программированию, но мне кажется, что лучший вариант - это отделение логики, данных и представления. Такие походы, как MVC придуманы уже давно и до сих пор успешно используются во многих компаниях. Только используются далеко не всеми. Я практически не видел подходы с разделением в проектах на Delphi и это ужасно. Народ почему-то пишет логику прямо в модулях представления или перепрыгивает сразу на трехуровневое программирование, когда код логики (тут любят применять красивое выражение "бизнес логика") выносится на промежуточный уровень.
Чем плох подход, когда логика находится прямо в модулях представления? Это идеальное бревно из дуба, у которого напрочь отсутствует гибкость. На последней работе за долгие годы существования компании было написано очень много кода с использованием устаревших технологий. Один только BDE чего стоит. Его уже давно используют, но при этом понимают, что вечно так продолжается не может. Так как логика доступа к базе данных внедрена непосредственно в модули представления (форм), то переход на другую технологию доступа к базам данных превращается в ад. Нужно перекалбасить все модули громадного количества проектов, созданных за долгие годы. Поэтому в компании особо не торопятся переходить на что-то новое и застряли в прошлом веке.
Переход на новые технологии обойдется компании громадными затратами ресурсов, а ведь нужно двигаться дальше. Невозможно просто остановить текущую разработку и заниматься переписыванием старья. За счет того, что в компании, о которой я говорю, работают далеко не тупые программеры (есть студенты, но большинство с большим опытом), они с успехом преодолевают недостатки BDE, но борьба с недостатками не может быть вечной.
Чужие модули и проекты я критиковать не буду (я уже много раз говорил, что я не критик, и критиков и без меня полно), я лучше расскажу на примере своего проекта, что и почему я делал. У меня есть один проект, который я выкинул в свободный доступ с открытым кодом - Database Modeller. В нем далеко не все идеально, потому что я создал его давно и на скорую руку на конкурс от компании Sun Microsystems. Просто однажды зашел на сайт и увидел, что у них мало проектов зарегистрировано на конкурс, а у меня как раз на работе была скука-печаль и я на скорую руку набросал проект, который может быть кто-то доведет до ума :). Хотя прекрасно понимаю, что никто не будет этого делать. Но может хоть источники кому пригодятся.
Короче. Тем кто не в курсе - проект позволяет визуально строить простую модель базы данных и применять созданную модель на базу. Программа также позволяет построить модель по текущей базе.
Программа должна работать с базами и всю логику работы с базой нужно выносить в отдельный класс, а точнее группу классов, которую назовем дружком. В классах, стоящих визуальный интерфейс, пишем только код визуального интерфейса, который может использовать движок для выполнения логики.
Существуют разные базы данных и в каждой из них могут быть разные реализации различных команд и операторов. Это выборка данных (SELECT) почти одинаков везде, а создание базы может отличается. Чтобы написать универсальный двиг, я создал базовый класс с именем DBInterface, в котором описаны все необходимые движку функции. Те функции, которые общие для всех баз данных, можно реализовать прямо в базовом классе.
Теперь, создаем наследника от этого базового класса DBInterface с именем DBInterfaceOracle, который реализует абстрактные функции с учетом базы данных Oracle. Таким образом, моя программа научится работать с этой БД. Можно создать еще одного наследника и реализовать необходимые функции с учетом любой другой базы данных. Таким образом, программа Database Modeller, сможет работать с любыми базами. Я сейчас точно не помню, но я кажется реализовал для примера работу с Oracle и MySQL.
Функции обращения к базе желательно было описать только в базовом классе, а в наследниках только использовать эти функции. В этом случае, если на рынке появится что-то более крутое для доступа к БД, достаточно будет переписать только один класс.
Благодаря такому разрешению, я могу изменять движок программы, не изменяя визуального представления, а так же изменять представление, не трогая движка. Работать с кодом удобно и непринужденно.
Точно так же построены программы CyD Careful Observer - Сетевой Монитор и Сетевые утилиты и безопасность, правда их исходные коды я показать не могу :). Поверьте мне на слово, в этих проектах код написан более аккуратно и логика напрочь отделена от представления. С большим распространением .NET 3.0 и 3.5, визуальный интерфейс будет переписан на WPF, при этом двиг программы переписывать не нужно будет.
Не знаю почему, но когда я писал на Delphi, то меня тянуло писать логику прямо в модулях визуального интерфейса. Когда я пишу на С# или Java, то меня тянет к отделению представления от логики. Мне сами языки нравятся и от программирования на С# в Visual Studio я получаю больше удовольствия.
Если ты знаком с Java, то советую посмотреть исходные коды этого о проекта. Он не идеален, но может быть, ты что-то найдешь для себя интересное в реализации. Напоминаю, проект создан на быструю руку.
Понравилось? Кликни Лайк, чтобы я знал, какой контент более интересен читателям. Заметку пока еще никто не лайкал и ты можешь быть первым
А в чем разница между RAD Studio и VS C#? И там и там обработчик событий создается двумя кликами.
Разницы технически нет, а вот не знаю почему, но на Delphi меня так и тянет написать код логики прямо в обработчиках событий.
Что сказать! Изучайте паттерны проектирования и антипаттерны тоже )))
Я не смотрел код, но судя по описанию движок использует паттерн Factory.
Вот уже 2 паттерна получается MVC и Factory.
Идеального кода наверно нет, поэтому я говорю о хорошем.
к идеальному коду все стремятся...
А какая разница твоему шефу была, Михаил. Если ты напишешь код, который будет работать!?
Могу предположить, что все эти паттерны трудно понять, если их не знаешь. И это усложняет код для понимания...
Честно говоря так и не понял сути разделения.
Можно ли хоть какой-то пример реализации привести? Хотя бы одну функцию/процедуру или больше. Для наглядности. Ну и желательно бы на Delphi.
однако...
2Евгений
Там основная идея в том, что GUI можно заменить на CMD без преобразования логики программы.
Например
procedure Button1Clock (Sender:TObject);
begin
// Вычисления
Label1.Caption := Result;
end;
А теперь поменяем GUI на командную строку, и получиться что нам придеться полностью переписать код.
А если бы вычисления у нас были в отдельной процедуре, то выглядело это примерно так
procedure Button1Clock (Sender:TObject);
begin
Label1.Caption := FuncName;
end;
А для команжной строки
WriteLn(FuncName);
Т.е. переписывать практически ничего ненадо. Пример канечно неахти какой, но надеюсь смысл будет понятен :)
Хотя я могу и неправельно понимать эту концепцию :)
отделение логики от интерфейса было желательно всегда.
Но всегда ли это нужно? Наверное реально автоматизировать разработку кода и, тем более, интерфейса. Тогда возникает вопрос, нужно ли разделять что-либо, если при компиляции все становится одинаковым и двоичным?
Даже если интерфейс генерится, разделение не будет лишним. Допустим, что ты писал программы на С++ и тут решил перейти на современный С#. Если у тебя код "два в одном", то тебе придется переписывать абсолютно все заново. Сделать это для большого проекта за короткий срок нереально. Намного проще будет переносить по частям - сначала интерфейс (логику вызывать через invoke), а потом логику.
Любую большую задачу лучше делить на более маленькие и решать их по отдельности.
да тут абсолютно Михаил прав. накладные расходы на резделение бизнес-логики, данных и остального обверточного кода невелики (если делать это с самого начала), а эффект по удобству доработки и масштабируемости - огромен. что с того, что в конечном двоичном счете все сольется воедино? машине удобно выполнять цельное, а человеку раскладывать на подзадачи и выполнять каждую по отдельности.
2 Alexo.
В основном я так и делаю, если правильно понимаю.
В обработчиках событий пишу по возможности только вызовы процедур и функций. А сами процедуры и функции хранятся в отдельных юнитах, и никак не взаимодействуют с интерфейсом. Мне кажется мигрировать со своей настольной БД на клиент-серверную смогу без полного переписывания кода. Хотя некоторые куски SQL-скриптов придется подрихтовать.
А вот перенос с Delphi на С я себе не представляю.
БД это отдельная тема, там главное всю работу с самой БД в отдельный класс выносить, что бы вызовы процедур не менялись, а менялось только тело процедуры доступа к БД.
Лично я признаю только MySQL :) У меня из за этого в универе были проблемы, у нас препод больше любит Access, и велел все делать там, и все же он меня не убедил :)
Я создаю GUI в C++ Builder'е.
Я правильно понял, что мои классы (в которых логика) не должны взаимодействовать напрямую с формой и ее элементами?
Т.е., если я пишу программу, которая тестирует знания (простые тесты), и у меня есть класс формы (и объект этого класса) и отдельно класс самого тестера (в котором логика), я не должен из класса тестера напрямую взаимодействовать с элементами формы? Я должен все методы взаимодействия с формой писать в классе формы, а класс Тестер должен просто обращаться к этим методам, передавая им нужные данные? Например, раньше, если мне нужно было вывести какую-то информацию на форму, я в "своем" классе писал метод, который обращался напрямую к элементам формы, и использовал их. Это ведь не правильно?
Михаил, насколько я знаю, вы не используете билдер, но поскольку и в Delphi и в C++ builder'е используется VCL, и один и тотже принцип создания приложений, вы можете ответить на вопрос? Естественно, ответы других участников мне тоже интересны.
Да, в твоей форме не должно быть никакой логики. В форме может создаваться класс, который будет реализовывать логику и ее вызывать.
Спасибо за ответ! Но хочу уточнить, так как в вашем сообщении не увидел ответа на мой вопрос.
-------------------------------------------------------
При создании программ я делаю примерно так:
1. Создаю интерфейс
2. Создаю новый юнит, а в нем класс (в котором логика)
3. Открываю cpp файл формы и подключаю к нему (include) h файл моего класса с логикой, чтобы в cpp файле формы можно было создать экземпляр (объект) класса с логикой.
4. Открываю cpp файл класса с логикой и подключаю к нему h файл формы, для того, что бы класс с логикой имел доступ к форме.
Потом просто в обработчиках событий формы вызываю методы класса. Когда я писал Тестер, у меня в обработчиках событий было в общей суме 6 строк (вызовы методов объекта Тестер).
------------------------------------------------------
Я все правильно делаю? Правильно ли, что объект Тестер обращается к элементам формы?
Вопрос на примере программы-тестера.
Например, в файле находятся тесты (вопрос, варианты ответа, правильный ответ, и т.п.). Когда пользователь нажимает кнопку "Следующий тест", у меня в обработчике события кнопки запускается примерно такой код:
void __fastcall TMainForm::NextButtonClick(TObject *Sender)
{
tester.SledyuschiyTest();
}
И метод "SledyuschiyTest()" занимается загрузкой следующего теста, выводом его на форму и т.п.
Это правильная архитектура?
Если твои обработчики событий не превышают 6 строк кода, то ты пишешь идеальный код. То, что твой класс тестера обращается к форме - не очень хорошо. Это значит, что он привязан к форме и знает о именах компонентов на ней. Нежелательно этого делать, потому что класс у тебя получается слишком привязанным к форме.
Ты приводишь правильный пример обработчика, но не понял, как у тебя класс Tester знает о форме? Обери эту связь. Если тебе нужно, чтобы на форме отображались какие-то данные от результата работы класса Tester, то перенос информации на форму делай в классе формы.
Делегат - это объявление метода, который будет вызываться в качестве обратного вызова, как событие.
Нет, ты не понял (или что-то путаешь).
Вот пример:
Voprositel, я не программист, но позволю себе тоже сюда "внедриться"
Для того чтобы организовать взаимодействие экземпляра класса формы с классом вашего Тестера, нужно в классе Тестера создать метод, имеющий в своём определении объявление TForm, например, или TLabel - в зависимости от того куда вы хотите выводить результаты
Затем из класса формы обращаться к методам тестера подставляя в метод ту форму (или метку), куда вы хотите вывести результат. При этом модуль где описан класс Тестера может ВООБЩЕ не ссылаться на класс вашей формы так как он в ней не нуждается. В дальнейшем этот факт облегчит повторное использование вашего Тестера, потому что он не привязан к конкретной форме.
Так форма будет отделена от модуля теста. А модуль теста не будет привязан ни к одной конкретной форме. Вместо этого он будет "привязан" к объектам типа TForm или TLabel или к любому другому по вашему желанию
Можно также перегрузать несколько методов. Создать несколько методов с одинаковыми именами, но с разными входными параметрами, Тогда вообще этому методу можно "подсовывать" почти что угодно.
Я мог в чём то ошибиться. Давно не открывал Дельфи. Поправте меня если где то я соврал.
Ты предлагаешь передавать в класс форму и класс будет заполнять данными форму.
Тестер.Метод(виуальный элемент);
Это слишком крутое связывание и гибкость отсутствует.
Чтобы этого небыло, нужно чтобы тестер ничего не знал о форме:
Визуальный элемент = Тестер.Метод();
В этом случае тестер ничег оне знает о том, где отображается результат работы и ты можешь его сипользовать где угодно и как угодно. Связываемость между формой и классом - нулевая.
Хотите найти еще что-то интересное почитать? Можно попробовать отфильтровать заметки на блоге по категориям.