Евгений Каратаев
И снова о проектировании классов. Больная тема и место применения множества трюков. Большинство программистов используют трюки по-разному. Видимо, есть три способа их применения - 1) неосознанно, 2) осознанно, но с затруднениями при выборе способа и 3) осознанно и, более того, трюки вычисляются.
Рассмотрим вопрос выбора пути при решении задачи типа "добавление новой функциональности". Имеется модуль в виде набора классов, который по функциональности частично подходит к тому, что надо получить. Имеется задача добавить в модуль некую функциональность. Имеется нежелание много работать и иметь в последующем с полученным кодом проблемы. При желании в эти условия задачи можно, полагаю, вписать практически любую программерскую задачу.
Рассмотрим выбор между двумя вариантами действий. Первый вариант - взять имеющийся класс, максимально подходящий к требуемому и изменить его путем модификации без получения нового класса. Скажем, поправить несколько функций или добавить несколько членов класса. Второй вариант - составить новый класс, унаследованный от максимально подходящего к требуемой функциональности и дописать к наследнику что ему не хватает или переопределить часть виртуальных функций базового. Первый вариант договоримся называть агрегированием, а второй - наследованием. Рассмотрим подробнее оба варианта, абстрагируясь от выбора конкретного языка программирования и содержания классов.
При агрегации мы не получаем нового класса и для обеих задач, старой и новой, используем один и тот же класс. Агрегацию мы можем получить не только как способ решить новую задачу, но и как способ исправить ошибки в старой задаче, поскольку исправления кода автоматически влияют на старую задачу. При агрегации к классу добавляется одно или два поля, благодаря которым и происходит различение старой и новой функциональности. А именно по значению этих полей. Например, добавленное поле имеет смысл номера версии, в зависимости от значения которой в модифицированном классе различается поведение нескольких функций. Этим способом мы можем избежать рутины с большим количеством модификаций задачи. Что является типичным признаком современного проекта. Добавляем поле, и при изменениях в спецификации корректируем поведение нескольких функций. Переопределять виртуальные функции по понятным причинам нет необходимости.
При наследовании мы получаем новый класс. Возможно, несколько. Новая функциональность реализуется исключительно в новом классе и имеющийся код этого никак не замечает и продолжает работать (надеюсь, без ошибок ;). В наследнике переопределяем одну или несколько виртуальных функций и при необходимости того добавляем поля данных. Примеры, как это делать, программисты сами могут привести из своей практики.
Сведем сравнительные различия в таблицу.
| Вид различия | Агрегация | Наследование |
| 1. Добавление новых полей | Скорее всего, поскольку следует различать состояния объекта как старого класса и как нового класса | Необязательно, поскольку функциональность может быть реализована скорее всего путем переопределения виртуальных функций. |
| 2. Переопределение виртуальных функций | Нет смысла | Скорее всего |
| 3. Сохранение работоспособности имеющегося кода | Сомнительно. Имеющийся код будет заводить объекты уже модифицированного класса, а модификация проводилась в целях иной задачи | Безусловно. Изменения не касаются имеющегося кода. |
| 4. Достижимость поставленной цели | Да, скорее всего. | Да, скорее всего. |
| 5. Наличие особых требований к имеющемуся классу | Нет. Если чего-то в нем не хватает, то это будет дописано. | Да. Базовый класс должен предусмотреть возможность наследования (например, объявить виртуальный деструктор) и предоставить часть своих функций как виртуальные. |
| 6. Наличие особого внимания к имеющемуся коду класса, контрольные точки | Да. Этапы инициализации и деинициализации объектов должны быть проконтролированы обязательно. Желательно с целью сохранения совместимости с предыдущим поведением объекта. | Нет, если средства реализации поддерживают автоматический вызов конструкторов и виртуальных деструкторов и да, если не поддерживают. |
| 7. Наличие особых требований к предыдущему оперативному окружению на этапе создания / удаления объекта | Никаких. Для окружения ничего не меняется. | Да. Точки создания объектов должны быть проверены на предмет создания объектов именно требуемого класса. |
| 8. Наличие особых требований к предыдущему оперативному окружению на этапе жизни объекта | Возможно, если модифицированный код меняет свое отношение к внешнему контексту. | Никаких. Предыдущее оперативное окружение видит и должно видеть базовый класс. Если переопределяемые виртуальные функции меняют отношение объекта к контексту, могут быть проблемы. |
| 9. Разрастание кода имеющейся программы | Да, безусловно. | Нет. |
Эти пункты следует учитывать, если проектирование классов производится не спонтанно и "по живому", а более-менее ответственно. Тем более, что всегда есть время обдумать свои шаги.
Приведем реальные примеры, когда перед программистом может ст
Наверняка у вас есть товары или услуги, продажа которых приносит вам максимальную прибыль. Для быстрого старта в сети вам необходимо создание посадочной страницы (одностраничного сайта), на которой будет размещена информация о маржинальных товарах/услугах интернет магазина. За 8 лет опыта разработки конверсионных страниц мы выработали оптимальную структуру, которая позволит привлекать через landing page больше продаж. На такую структуру «одевается» ваш контент — фирменный стиль, тексты, фотографии, уникальные торговые предложения, после чего страница выходит в свет. Разработка лендинга и запуск в сети — до 7 рабочих дней. Стоит отметить, что в разработку самой посадочной страницы входит и написание копирайтером продающих текстов для вашего бизнеса, чтобы каждый посетитель страницы захотел совершить покупку именно у вас. Результат: качественно разработаная продающая посадочная страница, которая готова приносить вам новых клиентов.