Українська
Інтерфейси та шаблони взаємодії
Чисто віртуальний деструктор та визначення функції
Чисто віртуальна функція може мати окреме визначення. Це не робить клас конкретним автоматично: чиста специфікація все одно задає обов’язок заміщення. Визначення може використовуватися через явну кваліфікацію базового методу в похідній реалізації.
Чисто віртуальний деструктор особливий тим, що потребує визначення, коли похідні об’єкти знищуються: базова частина завжди проходить власний етап знищення. Стандартний запис оголошення virtual ~Base() = 0; доповнюється визначенням Base::~Base() = default; поза класом. Саме відсутність визначення може проявитися помилкою компонування.
Для звичайного інтерфейсу часто простіше використати virtual ~Base() = default і чисто віртуальну предметну операцію. Інтерфейс уже абстрактний через цю операцію, тому додаткова чистота деструктора не дає переваги. Обирайте мінімальний запис, який виражає потрібний контракт.
Якщо видалення через інтерфейс заборонене, можливий захищений невіртуальний деструктор. Але для нашої колекції unique_ptr на інтерфейс потрібен публічний віртуальний деструктор. Узгодьте право знищення з моделлю володіння, перш ніж вибирати модифікатори.
Множинні інтерфейси та неоднозначність імен
Клас може реалізувати кілька незалежних інтерфейсів: лампа вмикається, регулює яскравість і має розклад. Клієнт, якому потрібне лише вмикання, отримує Switchable&, а не весь набір можливостей. Це зменшує залежність клієнта від конкретного пристрою.
Кілька баз можуть оголосити однакові імена. A::f() та B::f() явно вибирають реалізацію, а using-декларації можуть об’єднати потрібні перевантаження в наборі пошуку. Вони не усувають дубльовані підоб’єкти й не визначають, який предметний стан має бути єдиним.
Для двох інтерфейсів з однаковою чисто віртуальною сигнатурою одна похідна реалізація може задовольняти обидва контракти. Але однакова сигнатура не доводить однакового змісту. Якщо один reset очищує дані, а інший лише скидає лічильник, механічне об’єднання може суперечити очікуванню одного з клієнтів.
Найпростіший дизайн множинного наслідування часто складається з інтерфейсів без даних і одного власного стану конкретного класу. Складні мережі баз зі спільними полями потребують більше обґрунтування та перевірок, ніж саме ключове слово virtual.
Модель підоб’єктів та ABI
Об’єкт із кількома базами має відповідні базові підоб’єкти. Вказівник на одну базу не зобов’язаний мати таку саму числову адресу, як вказівник на іншу базу того самого повного об’єкта. Коректні перетворення виконують потрібне коригування адреси.
Не використовуйте reinterpret_cast як спосіб «перейти» між інтерфейсами за припущенням про розміщення. Потрібні мовні перетворення та, коли доречно, dynamic_cast. Концептуальна схема показує ролі підоб’єктів, а не байтові зміщення чи стабільну кількість прихованих вказівників.
Налагоджувач MSVC може показувати окремі службові поля й таблиці для різних баз. Вигляд залежить від компілятора, оптимізації й конкретного класу. У цій темі не використовуються недокументовані параметри дампу layout: для розуміння контракту достатньо схеми об’єктів і звичайного Locals.
Якщо бібліотека має стабільний двійковий інтерфейс, зміна баз чи віртуальних методів може порушити сумісність уже скомпільованого клієнта. Це причина обережно проєктувати публічні інтерфейси, хоча навчальні приклади збираються разом із вихідного коду.
Спостерігач: підписка та час життя
Шаблон «Спостерігач» (Observer) повідомляє зареєстрованих слухачів про подію. Джерело не повинно знати конкретний спосіб реакції кожного слухача; достатньо інтерфейсу update. У прикладі метеостанція передає нову температуру дисплею.
Невласний вказівник на слухача не продовжує його життя. Потрібно або відписати слухача до знищення, або забезпечити довше життя слухачів за джерело, або обрати іншу модель володіння. Порожня автоматична колекція адрес не є керуванням життям.
Окремий контракт стосується зміни підписок під час сповіщення. Якщо callback видалить елемент вектора, ітератор циклу може втратити чинність. У короткому прикладі зміна підписок усередині callback заборонена й не виконується; ширший дизайн може використати знімок або чергу змін.
Повторна підписка того самого об’єкта теж має визначену поведінку: тут вона не створює дублікат. Після відписки наступна подія не надходить. Ці два сценарії важливіші за наявність модного слова Observer у назві класу.
Команда: виконання та історія
Шаблон «Команда» (Command) представляє дію окремим об’єктом з execute і, за потреби, undo. Команда зберігає параметри дії та відомості, потрібні для відновлення. Редактор може зберігати різні команди через спільний інтерфейс, не розбираючи тип кожної дії.
Для додавання тексту undo видаляє саме доданий суфікс. Для заміни потрібен старий текст; просте повторне виконання з від’ємним аргументом не є універсальним відкатом. Історія повинна містити достатньо даних, щоб відновити попередній коректний стан.
Після undo команда переходить у redo. Якщо користувач виконує нову дію, стара гілка redo зазвичай відкидається, бо вона належить до іншого майбутнього. Це правило продукту, а не автоматична властивість vector. У прикладі воно реалізоване явно.
Операція має враховувати відмову виділення пам’яті: не можна змінити текст, а потім втратити можливість записати команду через помилку розширення історії. Навчальний редактор резервує місце перед виконанням; обмежений тип команди дозволяє просте обґрунтування наступних безвиняткових кроків.
Міксини, композиція та розмір ієрархії
Міксин (mixin) додає невелику спільну можливість: лічильник, ідентифікатор чи журналювання. Він не повинен перетворюватися на випадковий контейнер усіх допоміжних методів. Залежності й конфлікти імен усе одно залишаються частиною дизайну.
Композиція зручна, коли об’єкт має алгоритм чи компонент, а не є ним. Замовлення має політику знижки, редактор має історію команд, станція має слухачів. Такі зв’язки не вимагають перетворювати замовлення на підклас знижки або станцію на підклас дисплея.
Наслідування виправдане, коли клієнт дійсно працює через спільну абстракцію та потребує підстановки конкретних реалізацій. Композиція дозволяє змінювати частини незалежніше; ці підходи часто поєднуються: стратегія наслідує інтерфейс, а контекст володіє нею як полем.
Статичний поліморфізм через шаблони вибирає поведінку під час компіляції, динамічний – через runtime-об’єкт. Не слід оголошувати один універсально кращим. Вибір залежить від того, коли відомі типи, чи потрібна неоднорідна колекція та яким є контракт бібліотеки.