Ещё несколько причин почему вы сами того не осознавая пишете на Delphi сразу говнокод


Как известно, один класс может иметь несколько экземпляров. Но формы в Delphi обычно делаются непригодными для существования нескольких их экземпляров и практически невозможно делать их классы наследники. Особенно актуальной эта проблема становится в MDI приложениях, хотя те же проблемы встречаются практически повсеместно в программах на делфи.

Обработчик событий всегда один

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

Но в этом его и недостаток, потому что нельзя поместить на одно событие несколько обработчиков. Это всё потому что они являются тупо указателями на методы, а не полноценной реализацией шаблона проектирования Наблюдатель (англ. Observer), как это реализовано в интеллигентных языках программирования, вроде Java.

И такая беда повсюду в делфи, вы и сами наверняка сталкивались с этой проблемой. Вообще использование некоего подобия шаблона Наблюдатель в Delphi я встречал ровно один раз — это хорошо всем знакомый компонент TDataSource, который следит за компонентами TDataSet. Observer вообще трудновато реализовать на делфи, об этом я отдельно напишу.

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

Используйте переопределение методов вместо обработчиков событий

На самом деле, обычно, пока все обработчики событий у вас находятся внутри класса формы это особо большого неудобства не доставляет. Но такие же обработчики у вас могут использоваться для событий самой формы. Классическим примером служат события формы OnCreate, OnShow, OnActivate, OnPaint, OnClose. Несмотря на то что они выполняют внутреннюю логику формы, это публичные события и на них могут быть внешние обработчики.

Например форма редактора может вызывать форму настроек шрифта и по её закрытию применять новый шрифт. Т.е. нам нужно два обработчика события закрытия формы: один внутренний, который например сохраняет настройки в ini файл, и один внешний. Учитывая первую проблему мы этого сделать уже не можем.

А всё потому что нарушен контракт: класс должен выполнять обработку своих же событий не через регистрацию обработчиков на них, а внесением логики в защищённые (т.е. protected) методы а-ля DoSomthing. Методы должны быть protected для того чтобы можно было переопределить их в наследниках.

И вместо

procedure TForm.Form1OnClose(var Action: TCloseAction);
begin
  Action := caFree;
end;

нужно переопределять метод DoClose унаследованный от TCustomForm

procedure DoClose(var Action: TCloseAction); override;
begin
  Action := caFree;
  inherited; // Не забывайте дёргать унаследованный метод!
end;

И мы сможем освободить место для внешнего обработчика событий! И даже в классах наследниках сможем переопределять логику.

Беда в том что определить какой метод нужно переопределять зачастую геморно, а то и вовсе невозможно. Например для события OnShow мы можем переопределять либо Show либо DoShow. А вот на создание, здравый смысл подсказывает переопределять конструктор, но после создания объекта формы в нём ещё нет ни одного компонента, потому что они ещё должны быть загружены из dfm файла. В итоге мы имеем аж три варианта Create(AOwner: TComponent), DoCreate (предпочтительней) и AfterConstruction унаследованный ещё от TObject. А событие OnHelp вообще никак, ибо вызывается сразу классом TApplication (ну и не надо).

Я накидал небольшую табличку замен событий формы на переопределённые методы класса формы.

У формы могут быть много экземпляров, удаляйте переменные формы созданные компилятором

Формы в делфи, это практически Одиночки. При создании формы делфи генерирует новый юнит, внутри которого объявляется класс формы и сразу же переменная для этой формы. Также он записывает создание формы в файл проекта.

Сами компоненты сохраняются не в виде кода их создания, как это делается в Java или С#, а в отдельном файле *.dfm, что тоже очень хорошо. Джависты смотрят на делфи с завистью, хотя у них тоже есть инструменты отдельно описывать структуру форм, но они не стандартны и не очень популярны.

Созданное мастером форм делфи приложение будет создавать в памяти все формы, и это лишняя нагрузка. Зачем нам создавать форму настройки шрифта, если пользователь её не открывает? Поэтому обычно делают динамическое создание формы, как это делать написано в каждом учебнике. Но в них не написано, что чтобы достигнуть полной нирваны нужно ещё и удалить переменные формы из юнитов где они объявлены. Ведь форм может быть много а переменная одна, и та не используется. А если кто её заиспользует по невнимательности, то можно отхватить гейзенбаг. В моей практике было несколько таких случаев. Как это ни странно, dfm файлы всё равно корректно работают в этом случае, несмотря на то что в них описан экземпляр формы, а не свойства всего её класса.

PS

Как вы видите все эти проблемы свойственны не только формам. Но раз уж зашла о них речь то я хотел бы отметить другие популярные ошибки и дать пару советов:

  • Если у вас есть код который должен выполнятся один раз при открытии формы (например запрос в БД и отображение его в таблице), переопределяйте метод DoCreate (событие OnCreate) а не обработчики OnShow (будет выполнятся при каждом показе формы) или OnActivate (будет выполнятся при каждом входе в форму, хотя иногда именно такое и нужно для показа информации в реальном времени). А ещё лучше вынести этот функционал в отдельный публичный метод, например ExecuteQuery, и дёргать его после создания, как это делается например в диалогах открытия файлов.
  • В большинстве случаев, форму после закрытия нужно освобождать. Переопределяйте метод DoClose (событие OnClose) и устанавливайте Action := caFree.
  • Если вы всё таки используете форму как Одиночку, т.е. используете одну переменную объявленную в том же юните где и класс формы, то не забудьте поставить обработчик OnDestroy (да да, переопределить его не выйдет) а в нём установите значение переменной формы в nil. Это спасёт вас от глюков в выражениях вроде if Assigned(Form1) then.
  • Обработка Windows сообщений, типа WM_MINIMIZE и подобных, это большое зло. Панически избегайте их.
  • Про то что все таинственные названия, вроде Form1, нужно заменять на что-то более более внятное я даже писать не буду.
  • Я вас умоляю, избегайте модальных форм на всякую чепуху!
  • ДА ТЫСЯЧИ ИХ!!!
  • Не обращайтесь извне напрямую к компонентам формы, делайте свойства.

Выражение

FontSize := FontDialog.FontSize

лучше чем

FontSize := StrToInt(FontDialog.EditFontSize.Text)
Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s