Delphi notes шаблони в delphi (але не дженерики)

Напишу своїми словами, сподіваюся в доступній для масового читача формі.

Отже, припустимо у нас є пара класів: TSomeType1 і TSomeType2. які оголошені в SomeUnit.pas. І припустимо, що ці класи мають однаковий набір деяких властивостей і методів, але при цьому ці властивості і методи не оголошені у їх загального предка. Наприклад, нехай буде так:

Тут бачимо метод DoSomething. який є у обох класів. Але це два різних методу.

HINT: І припустимо, що у нас немає можливості вносити зміни в SomeUnit.pas. Це важливо для розуміння, чому саме такий підхід я описую.

А тепер припустимо, що у нас є завдання. написати клас, який буде працювати з екземплярами наших класів, викликаючи у них DoSomething (за деякими подій). А тому TSomeType1 і TSomeType2 - це різні класи, то нам доведеться писати два нових класу. Назвемо їх T1 і T2 відповідно. Як це можна зробити? Ну, наприклад, в лоб (файл MyUnit.pas):

HINT: Тут виклик DoSomething відбувається всього один раз в конструкторі класів T1 і T2. Це зовсім не практично, але це всього лише приклад, на якому я хочу продемонструвати описувану техніку.

І використовувати це десь в коді так:

А тепер уявіть, що наші класи T1 і T2 роблять набагато більше роботи. Тобто коду буде більше. Але при цьому, код буде збігатися. У наявності - дублювання коду. Як же його уникнути?

Думка перша - узагальнення

Спробуємо узагальнення. вони ж - дженерики. Наш MyUnit.pas буде виглядати так:

Але, на жаль, такий код не скомпілюється. Тому що для компіляції рядка FObject.DoSomething потрібно знати, що метод DoSomething є у типу T. а цього компілятор знати просто не може. І я не знайшов способів це якимось чином визначити. Можна явно вказати, від якого класу повинен бути успадкований тип T. або який інтерфейс тип T повинен підтримувати. Але в нашому прикладі це не підходить (ми ж не можемо вносити правки в SomeUnit.pas, пам'ятаєте?).

Думка друга - шаблони коду

Цей код треба буде підставити два рази. Але в один файл це не вийде зробити, тому створюємо два допоміжних файлу.

HINT: Якщо Ви не зовсім розумієте, що тут відбувається, то просто підставте вміст файлу X.inc в файли A.pas і B.pas замість директиви. І Ви побачите, що вийшло два повноцінних модуля.

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

Після цього, наш модуль MyUnit.pas треба переписати, він стане таким:

І ми можемо його використовувати точно так же, як це було показано вище.

Навіщо це потрібно

У разі, коли немає можливості внести зміни в початковий модуль типу SomeUnit.pas. така техніка може виявитися єдиною, що дозволяє вирішити такі завдання.

Наприклад, в VCL є компонент TCombobox. У режимі Style = csSimple або Style = csDropdown він працює як звичайний TEdit. І у TCombobox і у TEdit є набір схожих властивостей: MaxLength. SelStart. SelLength. SelText і т.п. Але ці властивості оголошені не в їх спільного предка (TWinControl), а в класах TCustomComboBox і TCustomEdit відповідно.

У попередній замітці я описував, як можна трохи змінити стандартну поведінку властивості MaxLength для спадкоємців від TCustomEdit (тобто TEdit. TMemo і т.п.). А для того, щоб його можна було застосувати і до TCustomComboBox - скористаємося описаною вище технікою.

Delphi notes шаблони в delphi (але не дженерики)

Плюси і мінуси даної техніки

  • Ми вирішили приватну задачу, коли немає можливості (чи ні бажання) внести зміни до початкового коду сторонньої бібліотеки.
  • Ми уникли дублювання коду.
  • Коли ми виносимо код в inc-файл, треба трохи більше уяви, ніж зазвичай: придумати імена для файлів і заміщаються типів, а також представляти, як це буде виглядати в підсумку.
  • IDE, працюючи з inc-файлами, заздалегідь не знає, в які місця цей inc-файл буде підставлятися. Як наслідок - тут не працює CodeInsight. можуть виникнути проблеми з рефакторингом і автоматичним форматуванням коду.

І такий нюанс. Отладчик з inc-файлами працює так само, як і зі звичайними pas-файлами. Але (як і з дженериками) ставлячи точку зупину у inc-файлі, це точка включається і для T1 і для T2. Вирішується забезпеченням умов для точки зупинки, наприклад: Self is T1.

Схожі статті