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ść.

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


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).


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.