Co jest lepsze: wiązka pobierających lub 1 metoda z parametrem ciągu wyboru?

15

Nasza dziedzina wiedzy obejmuje osoby chodzące boso po płycie rejestrującej ciśnienie. Dokonujemy rozpoznawania obrazu, co powoduje powstanie obiektów klasy „Foot”, jeśli ludzka stopa zostanie rozpoznana w danych czujnika.

Istnieje kilka obliczeń, które należy wykonać na danych stopy.

Teraz, który interfejs API byłby lepszy:

class Foot : public RecognizedObject  { 
  MaxPressureFrame getMaxPressureFrame();
  FootAxis getFootAxis();
  AnatomicalZones getAnatomicalZones();

  // + similar getters for other calculations

  // ...
}

Lub:

class Foot : public RecognizedObject {
  virtual CalculationBase getCalculation(QString aName);

  // ...
}

Teraz jest wiele plusów i minusów, które mogę wymyślić, ale tak naprawdę nie mogę zdecydować, które są najważniejsze. Uwaga: jest to aplikacja dla użytkowników końcowych, a nie biblioteka oprogramowania, którą sprzedajemy.

Jakakolwiek rada?

Niektórzy pro dla pierwszego podejścia mogą być:

  • KISS - wszystko jest bardzo konkretne. API, ale także implementacja.
  • mocno wpisane wartości zwracane.
  • dziedziczenie z tej klasy jest głupie. Nic nie można zastąpić, tylko dodać.
  • Interfejs API jest bardzo zamknięty, nic nie wchodzi, nic nie można zastąpić, więc mniej może się nie udać.

Niektóre wady:

  • Liczba pobierających będzie rosła, ponieważ każde nowe obliczenie, które wymyślimy, jest dodawane do listy
  • API jest bardziej prawdopodobne, że zmieni się, a jeśli wprowadzane są przełomowe zmiany, potrzebujemy nowej wersji API, Foot2.
  • w przypadku ponownego wykorzystania klasy w innych projektach możemy nie potrzebować wszystkich obliczeń

Niektórzy pro dla drugiego podejścia:

  • bardziej elastyczne
  • API jest mniej prawdopodobne, że zmieni się (zakładając, że otrzymaliśmy poprawną abstrakcję, jeśli nie, zmiana będzie kosztować więcej)

Niektóre wady:

  • luźno wpisane. Potrzebuje rzutów na każde połączenie.
  • parametr string - mam złe przeczucia (rozgałęzienie na wartościach string ...)
  • Nie ma obecnie przypadku użycia / wymogu, który wymagałby dodatkowej elastyczności, ale może być w przyszłości.
  • API nakłada ograniczenia: każde obliczenie musi pochodzić z klasy podstawowej. Uzyskanie obliczeń będzie wymuszone tą metodą 1, a przekazanie dodatkowych parametrów będzie niemożliwe, chyba że opracujemy jeszcze bardziej dynamiczny, superelastyczny sposób przekazywania parametrów, który jeszcze bardziej zwiększy złożoność.
Bgie
źródło
5
Możesz zrobić enumi włączyć jego wartości. Myślę jednak, że druga opcja jest zła, ponieważ odbiega od KISS.
Vorac
Trudniej jest znaleźć wszystkie zastosowania określonego obliczenia, jeśli użyjesz parametru ciągu, aby go uruchomić. Trudno być w 100%, że znalazłeś je wszystkie.
Konrad Morawski
Wykorzystaj to, co najlepsze z dwóch światów i napisz fasadę z grupą przywoływaczy getCalculation().
dokładnie
1
Wyliczenia są zdecydowanie lepsze niż struny! Nie myślałem o tym. Ograniczają API i zapobiegają nadużywaniu parametru łańcucha (jak łączenie tokenów i inne bzdury). Sądzę więc, że porównanie dotyczy opcji 1 i opcji 2 z wyliczeniem zamiast łańcucha.
Bgie

Odpowiedzi:

6

Myślę, że w pierwszym podejściu się opłaci. Magiczne ciągi znaków mogą powodować następujące problemy: błędy pisowni, niewłaściwe użycie, nietrywialne bezpieczeństwo typu zwrotu, brak uzupełnienia kodu, niejasny kod (czy ta wersja ma tę funkcję? Myślę, że dowiemy się w czasie wykonywania). Używanie wyliczeń rozwiąże niektóre z tych problemów, ale spójrzmy na podniesione wady:

  • Liczba pobierających będzie rosła, ponieważ każde nowe obliczenie, które wymyślimy, jest dodawane do listy

To prawda, że ​​może to być denerwujące, ale pozwala zachować porządek i ścisłość oraz umożliwia uzupełnianie kodu w dowolnym miejscu projektu w dowolnym nowoczesnym środowisku IDE, z dobrymi komentarzami nagłówkowymi, które są znacznie bardziej przydatne niż wyliczenia.

  • API jest bardziej prawdopodobne, że zmieni się, a jeśli wprowadzane są przełomowe zmiany, potrzebujemy nowej wersji API, Foot2.

To prawda, ale to naprawdę ogromny plus;) możesz zdefiniować interfejsy dla częściowych interfejsów API, a następnie nie musisz ponownie kompilować zależnej klasy, na którą nie wpływają nowsze interfejsy API (więc nie potrzebujesz Foot2). To pozwala na lepsze oddzielenie, zależność jest teraz od interfejsu, a nie implementacji. Co więcej, jeśli istniejący interfejs ulegnie zmianie, wystąpi błąd kompilacji w klasach zależnych, co doskonale zapobiega nieaktualnemu kodowi.

  • w przypadku ponownego wykorzystania klasy w innych projektach możemy nie potrzebować wszystkich obliczeń

Nie rozumiem, jak użycie magicznych ciągów lub wyliczeń pomoże w tym ... Jeśli dobrze rozumiem, albo umieścisz kod w klasie Foot, albo podzielisz go na kilka mniejszych klas, i to dotyczy obu opcji

użytkownik116462
źródło
Podoba mi się pomysł częściowych interfejsów API z interfejsami, wydaje się, że jest przyszłościowy. Pójdę z tym. Dziękuję Ci. Gdyby stał się zbyt zagracony (stopa implementuje zbyt wiele interfejsów), użycie kilku małych klas adapterów byłoby jeszcze bardziej elastyczne: Jeśli istnieje kilka odmian stóp z różnymi interfejsami API (np. Stopa, stopa, stopka, stopka), może istnieć mały adapter dla każdego z nich, aby jeden widget GUI mógł współpracować ze wszystkimi ...
Bgie
Zaletą używania selektora poleceń jest to, że można mieć implementację, która odbiera polecenie, którego nie rozumie, wywołuje metodę statycznego pomocnika dostarczoną z interfejsem. Jeśli polecenie reprezentuje coś, co można zrobić z prawie wszystkimi implementacjami przy użyciu podejścia ogólnego, ale które niektóre implementacje mogłyby wykonać za pomocą lepszych środków [rozważ np. IEnumerable<T>.Count], Takie podejście może pozwolić kodowi cieszyć się korzyściami płynącymi z nowego funkcje interfejsu podczas korzystania z implementacji, które je obsługują, ale zachowują zgodność ze starymi implementacjami.
supercat
12

Poleciłbym opcję 3: wyjaśnij, że obliczenia nie są nieodłączną częścią abstrakcji a Foot, ale działają na nim. Następnie możesz podzielić Footi obliczenia na osobne klasy, takie jak to:

class Foot : public RecognizedObject {
public:
    // Rather low-level API to access all characteristics that might be needed by a calculation
};

class MaxPressureFrame {
public:
    MaxPressureFrame(const Foot& aFoot); // Performs the calculation based on the information in aFoot
    //API for accessing the results of the calculation
};

// Similar classes for other calculations

W ten sposób nadal masz silne wpisywanie swoich obliczeń i możesz dodawać nowe bez wpływu na istniejący kod (chyba że popełniłeś poważny błąd w informacjach o kwocie ujawnionych przez Foot).

Bart van Ingen Schenau
źródło
Zdecydowanie ładniejsze niż opcje 1 i 2. To tylko krótki krok od wykorzystania wzorca projektowania strategii.
Brian
4
To może być właściwe. To może być przesada. Zależy od stopnia skomplikowania obliczeń. Zamierzasz dodać MaxPressureFrameStrategyFactory i MaxPressureFrameStrategyFactory? I zwykle zmienia Stopę w Obiekt Anemiczny.
user949300
Chociaż jest to miła alternatywa, odwrócenie zależności nie zadziałałoby w naszym przypadku. Stopa musi również działać jak pewien rodzaj mediatora, ponieważ niektóre obliczenia należy ponownie obliczyć, jeśli zmieni się inny (z powodu zmiany parametru przez użytkownika).
Bgie
@Bgie: Z takimi zależnościami między obliczeniami zgadzam się z @ user116462, że pierwsza opcja jest najlepsza.
Bart van Ingen Schenau