Jak uniknąć pisania wielu funkcji przekazywania w opakowaniu?

13

Mam klasę, która otacza inną klasę wspólnego typu podstawowego. Ponieważ interfejs typu podstawowego jest dość duży, wymaga to napisania wielu funkcji tranzytowych. Szukam sposobu, aby tego uniknąć.

Zróbmy przykład:

      Car
     /   \
Volvo     VolvoWithTrailer

Teraz muszę zaimplementować każdą funkcję w interfejsie samochodowym VolvoWithTrailer i wywołać odpowiednią funkcję na zawiniętym obiekcie Volvo, z wyjątkiem może GetMaxSpeed ​​(), która zwróci coś niższego. Zasadniczo będę miał wiele funkcji takich jak

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

Oczywistym sposobem rozwiązania tego problemu byłoby uczynienie VolvoWithTrailer podklasą Volvo

    Car
     |
   Volvo
     |
VolvoWithTrailer

ale wydaje się to naruszać zasadę faworyzowania kompozycji nad dziedziczeniem.

Jak inaczej mogę uniknąć pisania tych wszystkich opakowań (językiem jest C ++)? Lub - jeśli takie jest Twoje stanowisko - dlaczego powinienem je po prostu napisać / po prostu użyć dziedziczenia? Czy jest jakaś magia szablonu, która mogłaby w tym pomóc?

Sarien
źródło
2
Nie myślałem o tym zbyt wiele i myślę w Javie. Co z interfejsem „Trailer”? VolvoWithTrailer rozszerzy Volvo i wdroży interfejs przyczepy?
7
Preferuj kompozycję zamiast dziedziczenia tylko wtedy, gdy ma to sens.
Robert Harvey
@RobertHarvey: +1. To wytyczna, a nie „zasada”, a na pewno nie absolutne przykazanie.
Mason Wheeler
W Pythonie możesz to rozwiązać introspekcją (co C # nazywa odbiciem).
user16764
1
Czy twoje IDE nie obsługuje generowania kodu?
Amy Blankenship

Odpowiedzi:

5

Uważam, że powinieneś pisać wszystko, co potrzebujesz, aby poprawić długoterminową stabilność i łatwość konserwacji programu. Jaki jest sens unikania pisania 20 wierszy kodu dzisiaj, jeśli za każdym razem, gdy dotkniesz czegoś, co używa tego kodu lub wrócisz, aby utrzymać tę klasę, kosztuje to dodatkowe 5 minut lub więcej, ponieważ dzisiaj nie poświęciłeś czasu?

Zatem pytanie brzmi: „co musisz napisać?” Nie sądzę, abyś dostarczył wystarczającą ilość informacji, aby udzielić ostatecznej odpowiedzi, dlatego wymienię tylko kilka rzeczy, o których pomyślałem przy podejmowaniu decyzji:

1. Czy potrzebujesz obiektu Trailer samodzielnie, teraz czy w dającej się przewidzieć przyszłości?
Jeśli tak, masz całkiem niezły argument za tym, aby otoczyć oba złożone obiekty, ponieważ już będziesz musiał owinąć jeden lub drugi. Równie dobrze może być konsekwentny.

2. Czy którykolwiek z trzech typów (Car, Trailer, CarWithTrailer) w obszarze kodu, w który programiści często się pojawiają lub wyglądają na podobnego?
Jeśli tak, bądź bardzo ostrożny, ponieważ konsekwencje wyboru niewłaściwej rzeczy mogą być bardzo kosztownie amortyzowane przy każdym dotknięciu kodu. Jeśli nie, może nie mieć znaczenia, co wybierzesz. Po prostu wybierz kierunek i idź nim.

3. Co najłatwiej zrozumieć?
Czy jedno podejście rzuca się na ciebie, że ktoś idący za tobą natychmiast „dostanie” to, co próbujesz zrobić? Czy twoi koledzy z drużyny mają jakieś uprzedzenia, które mogą utrudniać utrzymanie jednego podejścia? Jeśli wszystkie rzeczy są równe, wybierz rozwiązanie, które nakłada najniższe obciążenie poznawcze.

4. Czy są jakieś dodatkowe zalety korzystania z jednego podejścia nad drugim?
Jedną rzeczą, która mnie zaskakuje, jest to, że pomysł owijania ma wyraźną zaletę, jeśli napiszesz, że CarWithTrailerWrapper może owinąć każdy samochód i dowolną przyczepę w CarWithTrailer. Następnie kończysz pisanie wszystkich metod przekazywania, ale piszesz je tylko raz , a nie raz dla każdej klasy samochodu.

To sprawia, że ​​początkowa inwestycja w pisanie metod przekazywania jest znacznie tańsza, gdy dodajesz więcej samochodów i przyczep. Pomaga także zmniejszyć pokusę parowania rzeczy zbyt bezpośrednio z Volvo lub VolvoWithTrailer, jednocześnie zmniejszając konsekwencje sprzężenia z CarWithTrailerWrapper, ponieważ dzięki tej jednej implementacji możesz zrobić znacznie więcej.

Może istnieją wyraźne zalety, które można uzyskać za darmo przy użyciu metody rozszerzenia, ale ich nie widzę.

OK, więc wygląda na to, że rozmawiałem o tym, by iść naprzód i wypełniać metody tranzytowe, dla nietrywialnych aplikacji.

Nie jestem programistą C ++, więc nie mam pojęcia, jakie narzędzia do generowania kodu są dostępne. IDE, którego używam, może generować metody z interfejsu, i mogę dodawać szablony kodu , dzięki którym pisanie przejść byłoby dość bezbolesne. Jeśli nie masz dostępu do IDE, które to robi, możesz dostać się daleko, pozwalając programowi Excel napisać kod za ciebie .

Amy Blankenship
źródło
3

Ponieważ interfejs typu podstawowego jest dość duży, wymaga to napisania wielu funkcji tranzytowych.

I jest twój problem.

Interfejsy powinny obsługiwać tylko jedną odpowiedzialność. Utrzymywanie ich cienkimi i skoncentrowanymi ogranicza ilość prac związanych z przekazywaniem danych, które mogą być konieczne w przypadku dekoratora, serwera proxy lub innego opakowania (oprócz uczynienia prostego klasy do używania, testowania, utrzymywania i rozszerzania).

Telastyn
źródło
5
Oh, co? Jedna klasa może implementować wiele interfejsów, a interfejsy mogą dziedziczyć po sobie. Tak więc nawet jeśli interfejs jest dość mały, nie ma to nic wspólnego z tym, czy klasy, które je implementują, będą wymagały wielu funkcji lub funkcji tranzytowych. Im mniejsze i lepiej zdefiniowane klasy , tym bardziej prawdopodobne jest, że będziesz potrzebował funkcji tranzytowych.
Amy Blankenship