Was ist besser: ein Haufen Getter oder eine Methode mit einem Auswahlstringparameter?

15

Unser Wissensgebiet umfasst Menschen, die mit bloßen Füßen über eine Druckplatte laufen. Wir machen eine Bilderkennung, die Objekte der Klasse 'Fuß' ergibt, wenn ein menschlicher Fuß in den Sensordaten erkannt wird.

Es gibt verschiedene Berechnungen, die an den Daten des Fußes durchgeführt werden müssen.

Nun, welche API wäre besser:

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

  // + similar getters for other calculations

  // ...
}

Oder:

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

  // ...
}

Nun gibt es eine Menge Vor- und Nachteile, die ich mir einfallen lassen kann, aber ich kann mich nicht wirklich entscheiden, welche die wichtigsten sind. Hinweis: Dies ist eine Endbenutzeranwendung, keine von uns verkaufte Softwarebibliothek.

Irgendein Rat?

Einige Profis für den ersten Ansatz könnten sein:

  • KISS - alles ist sehr konkret. Die API, aber auch die Implementierung.
  • stark typisierte Rückgabewerte.
  • Das Erben von dieser Klasse ist narrensicher. Nichts kann überschrieben, nur hinzugefügt werden.
  • API ist sehr geschlossen, nichts geht rein, nichts kann überschrieben werden, so dass weniger schief gehen kann.

Einige Con's:

  • Die Anzahl der Getter wird zunehmen, da jede neue Berechnung, die wir erfinden, zur Liste hinzugefügt wird
  • Es ist wahrscheinlicher, dass sich die API ändert. Wenn wichtige Änderungen vorgenommen werden, benötigen wir eine neue API-Version, Foot2.
  • Im Falle der Wiederverwendung der Klasse in anderen Projekten benötigen wir möglicherweise nicht jede Berechnung

Einige Profis für den zweiten Ansatz:

  • flexibler
  • Es ist weniger wahrscheinlich, dass sich die API ändert (vorausgesetzt, wir haben die richtige Abstraktion, wenn nicht, kostet eine Änderung mehr).

Einige Con's:

  • lose getippt. Muss bei jedem Anruf besetzt werden.
  • der String-Parameter - ich habe schlechte Gefühle darüber (Verzweigung auf String-Werte ...)
  • Es gibt keinen aktuellen Anwendungsfall / keine aktuelle Anforderung, die die zusätzliche Flexibilität vorschreibt, aber möglicherweise in der Zukunft.
  • Die API legt Einschränkungen fest: Jede Berechnung muss von einer Basisklasse abgeleitet werden. Das Erhalten einer Berechnung wird durch diese 1 Methode erzwungen, und das Übergeben zusätzlicher Parameter wird unmöglich sein, es sei denn, wir entwickeln eine noch dynamischere, superflexible Methode zum Übergeben von Parametern, die die Komplexität noch weiter erhöht.
Bgie
quelle
5
Sie könnten ein machen enumund seine Werte einschalten. Trotzdem denke ich, dass die zweite Option böse ist, weil sie von KISS abweicht.
Vorac
Es ist schwieriger, alle Verwendungen einer bestimmten Berechnung zu finden, wenn Sie einen Zeichenfolgenparameter zum Auslösen verwenden. Schwer zu 100% zu sein, du hast sie alle gefunden.
Konrad Morawski
Nimm das Beste aus zwei Welten und schreibe eine Fassade mit einer Reihe von Anrufen getCalculation().
Nalply
1
Aufzählungen sind definitiv besser als Streicher! Daran hatte ich nicht gedacht. Sie schränken die API ein und verhindern den Missbrauch des String-Parameters (wie das Zusammenfassen von Tokens und anderem Mist). Ich denke, der Vergleich erfolgt zwischen Option 1 und Option 2 mit enum anstelle von string.
Bgie

Antworten:

6

Ich denke im ersten Ansatz wird sich auszahlen. Magische Zeichenfolgen können die folgenden Probleme verursachen: Tippfehler, Missbrauch, nicht triviale Sicherheit beim Zurückgeben, fehlende Code-Vervollständigung, unklarer Code (hatte diese Version diese Funktion? Ich denke, wir werden es zur Laufzeit herausfinden). Durch die Verwendung von Aufzählungen werden einige dieser Probleme behoben. Sehen wir uns jedoch die Nachteile an, die Sie angesprochen haben:

  • Die Anzahl der Getter wird zunehmen, da jede neue Berechnung, die wir erfinden, zur Liste hinzugefügt wird

Das kann zwar ärgerlich sein, hält aber die Dinge schön und streng und gibt Ihnen überall in Ihrem Projekt in einer modernen IDE Code-Vervollständigung mit guten Überschriften-Kommentaren, die viel nützlicher sind als Aufzählungen.

  • Es ist wahrscheinlicher, dass sich die API ändert. Wenn wichtige Änderungen vorgenommen werden, benötigen wir eine neue API-Version, Foot2.

Stimmt, aber das ist eigentlich ein riesiger Profi;) Sie können Schnittstellen für Teil-APIs definieren und müssen dann keine abhängigen Klassen neu kompilieren, die nicht von neueren APIs betroffen sind (also kein Foot2 erforderlich). Das ermöglicht eine bessere Entkopplung, die Abhängigkeit ist jetzt von der Schnittstelle und nicht von der Implementierung. Darüber hinaus tritt bei Änderungen an einer vorhandenen Schnittstelle in abhängigen Klassen ein Kompilierungsfehler auf. Dies ist hilfreich, um veralteten Code zu vermeiden.

  • Im Falle der Wiederverwendung der Klasse in anderen Projekten benötigen wir möglicherweise nicht jede Berechnung

Ich verstehe nicht, wie die Verwendung von magischen Zeichenfolgen oder Aufzählungen dabei helfen wird. Wenn ich das richtig verstehe, fügen Sie entweder den Code in die Foot-Klasse ein oder unterteilen ihn in ein paar kleinere Klassen. Dies gilt für beide Optionen

user116462
quelle
Ich mag die Idee der partiellen APIs mit Schnittstellen, sie scheinen zukunftssicher zu sein. Ich werde mitgehen. Vielen Dank. Sollte es zu unübersichtlich werden (Fuß implementiert zu viele Schnittstellen), wäre die Verwendung mehrerer kleiner Adapterklassen noch flexibler: Wenn mehrere Fußvarianten mit unterschiedlichen APIs existieren (wie Fuß, Menschfuß, Hundefuß, MenschfußVersion2), könnte ein kleiner Adapter vorhanden sein für jeden, damit ein GUI-Widget mit allen von ihnen arbeiten kann ...
Bgie
Ein Vorteil der Verwendung eines Befehlsselektors besteht darin, dass eine Implementierung einen Befehl empfangen kann, von dem sie nicht versteht, dass sie eine statische Hilfsmethode aufruft, die mit der Schnittstelle bereitgestellt wird. Wenn der Befehl etwas darstellt, das mit fast allen Implementierungen unter Verwendung eines Allzweckansatzes ausgeführt werden kann, das jedoch bei einigen Implementierungen möglicherweise mit besseren Mitteln erreicht werden kann (z. B. IEnumerable<T>.Count), kann Code mit einem solchen Ansatz die Leistungsvorteile von new nutzen Schnittstellenfunktionen bei Verwendung von Implementierungen, die diese unterstützen, aber mit alten Implementierungen kompatibel bleiben.
Supercat
12

Ich würde Option 3 empfehlen: Stellen Sie klar, dass die Berechnungen kein wesentlicher Bestandteil der Abstraktion von a sind Foot, sondern darauf basieren . Dann können Sie Footdie Berechnungen wie folgt in separate Klassen aufteilen :

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

Auf diese Weise haben Sie immer noch eine starke Typisierung Ihrer Berechnung und können neue hinzufügen, ohne den vorhandenen Code zu beeinträchtigen (es sei denn, Sie haben einen schwerwiegenden Fehler bei den von bereitgestellten Betragsinformationen gemacht Foot).

Bart van Ingen Schenau
quelle
Auf jeden Fall schöner als die Optionen 1 und 2. Dies ist nur ein kurzer Schritt von der Nutzung des Strategie-Design-Musters entfernt.
Brian
4
Das könnte angebracht sein. Das könnte übertrieben sein. Kommt darauf an, wie komplex die Berechnungen sind. Fügen Sie eine MaxPressureFrameStrategy und eine MaxPressureFrameStrategyFactory hinzu? Und es neigt dazu, Fuß in ein anämisches Objekt zu verwandeln.
user949300
Obwohl dies eine gute Alternative ist, würde das Umkehren der Abhängigkeiten in unserem Fall nicht funktionieren. Der Fuß muss auch als eine Art Vermittler fungieren, da einige Berechnungen neu berechnet werden müssen, wenn sich ein anderer ändert (weil der Benutzer einen Parameter ändert oder so).
Bgie
@Bgie: Mit solchen Abhängigkeiten zwischen den Berechnungen stimme ich @ user116462 zu, dass die erste Option die beste ist. "
Bart van Ingen Schenau