Как избежать написания множества сквозных функций в оболочке?

13

У меня есть класс, который оборачивает другой класс общего базового типа. Поскольку интерфейс базового типа довольно большой, это требует написания множества сквозных функций. Я ищу способ избежать этого.

Давайте сделаем пример:

      Car
     /   \
Volvo     VolvoWithTrailer

Теперь мне нужно реализовать каждую функцию в автомобильном интерфейсе для VolvoWithTrailer и вызвать соответствующую функцию для обернутого объекта Volvo, за исключением, возможно, GetMaxSpeed ​​(), который возвратит что-то меньшее. В основном у меня будет много функций, таких как

int VolvoWithTrailer::GetNumSeats() {
  return mVolvo.GetNumSeats()
}

Очевидный способ решить эту проблему - сделать VolvoWithTrailer подклассом Volvo.

    Car
     |
   Volvo
     |
VolvoWithTrailer

но это, кажется, нарушает принцип предпочтения композиции по наследству.

Как еще можно избежать написания всех этих оболочек (язык C ++)? Или - если это ваша позиция - почему я должен просто написать их / просто использовать наследование? Есть ли какое-нибудь волшебство шаблона, которое могло бы помочь с этим?

Sarien
источник
2
Не слишком много думал об этом и думал на Java. А как насчет интерфейса «Трейлер»? VolvoWithTrailer расширит Volvo и реализует интерфейс Trailer?
7
Предпочитайте композицию наследованию только тогда, когда это имеет смысл.
Роберт Харви,
@RobertHarvey: +1. Это руководство, а не «принцип» и, конечно, не абсолютная заповедь.
Мейсон Уилер
В Python вы можете решить это с помощью самоанализа (то, что в C # называется отражением).
user16764
1
Ваша IDE не обрабатывает генерацию кода?
Эми Бланкеншип

Ответы:

5

Я считаю, что вы должны написать все, что вам нужно, чтобы улучшить долговременную стабильность и ремонтопригодность программы. Какой смысл избегать написания 20 строк кода сегодня, если каждый раз, когда вы касаетесь чего-то, что использует этот код, или возвращаетесь, чтобы поддержать этот класс, это стоит вам дополнительные 5 минут или больше, потому что вы не потратили время сегодня?

Таким образом, вопрос становится «что вам нужно написать?» Я не думаю, что вы предоставляете достаточно информации, чтобы иметь возможность дать окончательный ответ, поэтому я просто перечислю некоторые вещи, о которых я бы подумал при принятии решения:

1. Вам нужен объект Trailer сам по себе, сейчас или в обозримом будущем?
Если это так, у вас есть довольно хороший аргумент для создания оболочки вокруг обоих составных объектов, поскольку вам уже придется оборачивать один или другой. Может также быть последовательным.

2. Находится ли какой-либо из трех типов (Car, Trailer, CarWithTrailer) в той области кода, в которую разработчики часто заходят или, по-видимому, становятся таковой?
Если это так, будьте очень осторожны, потому что последствия выбора неправильной вещи могут очень дорого амортизироваться при каждом прикосновении к коду. Если нет, это может не иметь никакого значения, что вы решите. Просто выберите направление и следуйте по нему.

3. Что легче всего понять?
Подходит ли вам какой-то подход, что кто-то, идущий за вами, сразу «получит» то, что вы пытаетесь сделать? Есть ли у ваших товарищей по команде особые предубеждения, которые могут затруднить поддержание одного подхода? Если все вещи равны, выберите решение, которое налагает наименьшую когнитивную нагрузку.

4. Есть ли какие-либо дополнительные преимущества использования одного подхода перед другим?
Одна вещь, которая бросается в глаза, это то, что идея обертки имеет явное преимущество, если вы напишите, что ваш CarWithTrailerWrapper может обернуть любой автомобиль и любой трейлер в CarWithTrailer. Затем вы заканчиваете писать все свои сквозные методы, но вы пишете их только один раз , а не один раз для каждого автомобильного класса.

Это делает ваши первоначальные инвестиции в написание пассивных методов намного дешевле, так как вы добавляете больше автомобилей и трейлеров. Это также помогает уменьшить искушение связать вещи слишком напрямую с Volvo или VolvoWithTrailer, одновременно уменьшая последствия соединения с CarWithTrailerWrapper, поскольку вы можете сделать намного больше с помощью только одной реализации.

Может быть, есть определенные преимущества, которые вы получите бесплатно, используя метод расширения, но я их не вижу.

Итак, похоже, что я сам обговорил идти вперед и заполнить сквозные методы для нетривиальных приложений.

Я не программист на C ++, поэтому я понятия не имею, какие инструменты генерации кода вам доступны. Используемая мной среда IDE может генерировать методы из интерфейса, и я могу добавить шаблоны кода , которые сделают написание проходов довольно безболезненным. Если у вас нет доступа к IDE, которая делает это, вы можете довольно далеко позволить Excel написать код для вас .

Эми Бланкеншип
источник
3

Поскольку интерфейс базового типа довольно большой, это требует написания множества сквозных функций.

И есть твоя проблема.

Интерфейсы должны обрабатывать только одну ответственность. Сохранение их тонкими и сфокусированными ограничивает объем работы, которую вам может понадобиться в случае декоратора, прокси или другой оболочки (в дополнение к упрощению использования, тестирования, поддержки и расширения класса).

Telastyn
источник
5
А что? Один класс может реализовывать несколько интерфейсов, и интерфейсы могут наследоваться друг от друга. Таким образом, даже если интерфейс достаточно мал, это не имеет никакого отношения к тому, понадобятся ли классам, которые их реализуют, много функций или сквозных функций. Чем меньше и более четко определены классы , тем более вероятно, будет , что вы будете нужны сквозная функций.
Эми Бланкеншип