Расширение публичного интерфейса в наследнике, как это решается?
Всем привет.
Сразу опишу ситуацию, а потом сформулирую вопрос.
Есть класс Car, в котором полями являются компоненты Engine, Gearbox, Body и т.д. со своими публичными интерфейсами.
Допустим, мы хотим расширить публичный интерфейс компонента Body и добавить корпусу машины выдвигающийся спойлер.
Решение, которое сразу приходит в голову:
Ок, создаем наследника Body, назовем его WingedBody.
Выдвигаться этот спойлер будет исходя из какой-то логики класса Car, т.е. вызывать метод deploySpoiler() должен класс Car (а точнее его наследник).
Создаем наследника Car под названием WingedCar, и тут возникает вопрос: как пользоваться новой функциональностью класса WingedBody? Ведь от класса Car мы унаследовали поле с типом Body, а нужен нам WingedBody.
Несколько очевидных решений:
1) Вариант в лоб - приведение типа, то бишь downcasting от Body к WingedBody. Некрасиво и неэффективно.
2) Создаем дополнительное поле с типом WingedBody и новую функциональность вызываем через него. Минус - нужно поддерживать равенство обоих полей (старый с типом Body и новый c типом WingedBody должны быть одним и тем же объектом).
3) Улучшенный вариант второго решения. Создаем интерфейс IDeployableSpoiler, реализуем его в классе WingedBody, а в классе WingedCar создаем поле с типом IDeployableSpoiler а не WingedBody. Более понятное решение, но все равно мы по сути плодим лишние поля.
Есть мнение, что такая ситуация в реальной работе возникает очень редко и является ошибкой проектирования. Если вы так считаете, то обоснуйте это пожалуйста, потому что, как мне кажется, заранее всю функциональность (а соответственно и публичный интерфейс) предусмотреть невозможно (а если это либа - значит и дописать родительский класс нельзя).
Вопрос уже звучал, но повторю для наглядности. Как бы вы решали подобную задачу?
У класса Car есть контейнер объектов IAerodynamics, в котором содержатся все объекты, влияющие на аэродинамику. Хочешь - добавляешь спойлер, хочешь - обвесы, хочешь - снимаешь всё.
согласен с Армянское Радио, более того, теряется смысл выделения компонентов машины в отдельный уровень абстракции (например car - body - spoiler), а это значит, что компоненты нельзя переиспользовать. В таком случае уже проще написать God Class Car, который будет все содержать и все что нужно делать. В любом случае, решение далеко не красивое.
Армянское Радио: Зачем кастовать, у них же общий интерфейс. В функцию передаётся вектор направления движения (и/или направления ветра) и возвращается вектор силы, производимой этим аэродинамическим элементом.
maagames.ru: Ситуация с автомобилем приведена просто для примера. На самом деле меня интересует более общая ситуация, когда интерфейс как раз таки не общий, а перепроектировать родительский класс по каким-то причинам нельзя.
rxlrxl: Тогда нужно исходить исходя из того, что ты вообще можешь сделать. Хоть шаблонный класс, хоть расширение интерфейса - ничего этого не поможет, если ты не можешь внести изменения в класс (который Car). Если
Не нужно использовать наследование. Продолжайте использовать агрегацию.
Машина состоит из двигателя, кузова и колес. Кузов состоит из дверей, крыльев, спойлера и так далее.
Там где спойлера нет возвращаем null. Как-то так...
Армянское Радио: вы не так поняли, я люблю шаблоны, но в описанной мной ситуации необходимость их применения слишком мала, чтобы это выглядело хорошей идеей)
rxlrxl: рассмотрим плюсы:
+вы смещаете ошибки с типами на этап компиляции
+у вас не будет риска натворить дел с указателями
+вам не придется во время выполнения выяснять тип обрабатываемого объекта.
Реально, можно написать два скетча и посмотреть, какой ужаснее.