Czy źle jest tworzyć klasy, których jedynym celem jest niejawna konwersja na inną klasę?

10

Wyobraźmy sobie sytuację, w której korzystamy z biblioteki, która pozwala tworzyć Circleobiekty, w których można określić promień i środek okręgu, aby je zdefiniować. Jednak z jakiegoś powodu wymaga również wymaganego flavourparametru. Powiedzmy teraz, że naprawdę muszę korzystać Circlez własnej aplikacji, ale na potrzeby mojej aplikacji mogę ustawić smak za Flavours.Cardboardkażdym razem.

Aby to „rozwiązać”, tworzę własną Circleklasę w innej przestrzeni nazw, która przyjmuje tylko radiusi centerjako parametry, ale ma niejawny konwerter na Circleklasę biblioteki zewnętrznej, która po prostu tworzy Circle(this.radius, this.center, Flavours.Cardboard)obiekt. Wszędzie, gdzie potrzebuję innego rodzaju Circle, pozwalam na automatyczną konwersję.

Jakie są konsekwencje stworzenia takiej klasy? Czy są jakieś lepsze rozwiązania? Czy miałoby to jakąkolwiek różnicę, gdyby moja aplikacja była interfejsem API zbudowanym na tej zewnętrznej bibliotece, przeznaczonym do użytku przez innych programistów?

użytkownik3002473
źródło
To wydaje się być odmianą Wzorca Adaptera , chociaż wydaje się mieć tutaj wątpliwą wartość (jest to jedynie wygoda w unikaniu ustawiania parametru).
Robert Harvey
5
Dlaczego nie stworzyć MakeCircle funkcji ?
user253751
1
@immibis Thay to moja pierwsza myśl. Kiedy tworzę gry, często tworzę funkcję zgodną z makePlayertym, że sama akceptuje tylko współrzędne, aby umieścić gracza, ale deleguje do znacznie bardziej złożonego konstruktora.
Carcigenicate

Odpowiedzi:

13

Chociaż nie zawsze jest tak źle, zdarza się, że konwersje niejawne są najlepszą opcją.

Są problemy.

  1. Niejawne konwersje nie są zbyt czytelne.
  2. Ponieważ tworzysz nowy obiekt, żadne zmiany w oryginalnym obiekcie nie będą widoczne dla tego, na który konwertujesz, co prowadzi do błędów.
  3. Jeśli wyrzucasz przedmiot, to dodatkowe śmieci do oczyszczenia.
  4. Jeśli wystąpi problem, stanie się to, gdy nastąpi konwersja, a nie kiedy rzecz zostanie stworzona, co powoduje, że błędy są trudniejsze do wyśledzenia.

Ogólnie rzecz biorąc, istnieją lepsze rozwiązania.

  1. Stwórz własne cienkie opakowanie, które wewnętrznie używa innej klasy / frameworka.
  2. Stwórz metodę pomocniczą, która pobierze żądane argumenty konstruktora i dostarczy ten, który jest naprawiony, zwracając prawdziwy obiekt bez konieczności podawania argumentu, na którym ci nie zależy.
  3. Dziedzicz po klasie problemów i podaj własnego, ładniejszego konstruktora.
  4. Uświadom sobie, że przekazanie dodatkowego argumentu tak naprawdę nie jest wielkim problemem.

Osobiście uważam, że # 2 jest najprostszy do wdrożenia i najmniej uciążliwy dla projektu. Inne mogą być w porządku, biorąc pod uwagę sytuację i co jeszcze próbujesz zrobić z tymi klasami.

Niejawna konwersja jest ostatecznością i wydaje mi się, że naprawdę warto, gdy mam funktory w stylu C ++, które próbuję wykonać - obiekty strategii, które domyślnie przekonwertowałem na typy delegowane.

Telastyn
źródło
1

Biorąc pod uwagę opisywany scenariusz, można o tym pomyśleć w kategoriach zastosowania funkcji częściowej.

Konstruktor jest funkcją (przynajmniej teoretycznie; w języku C # można utworzyć „funkcję fabryczną”, która wywołuje konstruktor):

Func<double, Point, Flavour, Circle> MakeCircle = (r, p, f) => new Circle(r, p, f);

w przypadku częściowego zastosowania wystarczą:

public static Func<T1, T2, R> Partial<T1, T2, T3, R>(this Func<T1, T2, T3, R> func, T3 t3)
   => (t1, t2) => func(t1, t2, t3);

Możesz teraz uzyskać konstruktor, który wymaga tylko 2 parametrów:

Func<double, Point, Circle> MakeCardboardCircle = Circle.Partial(Flavours.Cardboard)

Teraz masz funkcję fabryczną z żądanymi parametrami

Circle c = MakeCardboardCircle(1.0, new Point(0, 0)))

BTW, jest to wyraźnie równoważne z opcją 2 powyżej, tylko z bardziej funkcjonalnej perspektywy.

la-yumba
źródło