Напишу своїми словами, сподіваюся в доступній для масового читача формі.
Отже, припустимо у нас є пара класів: 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 - скористаємося описаною вище технікою.
Плюси і мінуси даної техніки
- Ми вирішили приватну задачу, коли немає можливості (чи ні бажання) внести зміни до початкового коду сторонньої бібліотеки.
- Ми уникли дублювання коду.
- Коли ми виносимо код в inc-файл, треба трохи більше уяви, ніж зазвичай: придумати імена для файлів і заміщаються типів, а також представляти, як це буде виглядати в підсумку.
- IDE, працюючи з inc-файлами, заздалегідь не знає, в які місця цей inc-файл буде підставлятися. Як наслідок - тут не працює CodeInsight. можуть виникнути проблеми з рефакторингом і автоматичним форматуванням коду.
І такий нюанс. Отладчик з inc-файлами працює так само, як і зі звичайними pas-файлами. Але (як і з дженериками) ставлячи точку зупину у inc-файлі, це точка включається і для T1 і для T2. Вирішується забезпеченням умов для точки зупинки, наприклад: Self is T1.