Quale è meglio: un gruppo di getter o 1 metodo con un parametro stringa di selezione?


15

Il nostro dominio di conoscenza coinvolge le persone che camminano a piedi nudi su una piastra di registrazione della pressione. Eseguiamo il riconoscimento di immagini che si traducono in oggetti della classe "Foot", se un piede umano viene riconosciuto nei dati del sensore.

Esistono diversi calcoli che devono essere eseguiti sui dati del piede.

Ora, quale API sarebbe meglio:

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

  // + similar getters for other calculations

  // ...
}

O:

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

  // ...
}

Ora, ci sono molti pro e contro che posso inventare, ma non riesco davvero a decidere quali sono i più importanti. Nota, questa è un'applicazione per l'utente finale, non una libreria software che vendiamo.

Qualche consiglio?

Alcuni pro per il primo approccio potrebbero essere:

  • BACIO - tutto è molto concreto. L'API, ma anche l'implementazione.
  • valori di ritorno fortemente tipizzati.
  • ereditare da questa classe è infallibile. Nulla può essere ignorato, solo aggiunto.
  • L'API è molto chiusa, non succede nulla, nulla può essere ignorato, quindi meno può andare storto.

Alcuni trucchi:

  • Il numero di getter aumenterà, poiché ogni nuovo calcolo che inventiamo viene aggiunto all'elenco
  • È più probabile che l'API cambi e, se vengono introdotte modifiche, abbiamo bisogno di una nuova versione dell'API, Foot2.
  • in caso di riutilizzo della classe in altri progetti, potrebbe non essere necessario ogni calcolo

Alcuni pro per il secondo approccio:

  • più flessibile
  • l'api ha meno probabilità di cambiare (supponendo che l'astrazione sia corretta, altrimenti il ​​cambiamento costerà di più)

Alcuni trucchi:

  • vagamente digitato. Ha bisogno di lanciare ad ogni chiamata.
  • il parametro string - Ho dei cattivi sentimenti a riguardo (ramificazione sui valori delle stringhe ...)
  • Non esiste un caso / requisito d'uso attuale che imponga una maggiore flessibilità, ma potrebbe esserci in futuro.
  • l'API impone restrizioni: ogni calcolo deve derivare da una classe base. Ottenere un calcolo sarà forzato con questo metodo 1 e passare parametri aggiuntivi sarà impossibile, a meno che non creiamo un modo ancora più dinamico e super-flessibile di passare parametri che aumenta ulteriormente la complessità.

5
Potresti crearne uno enume accenderne i valori. Tuttavia, penso che la seconda opzione sia malvagia, perché si discosta dal KISS.
Vorac,

È più difficile trovare tutti gli usi di un calcolo specifico se si utilizza un parametro stringa per attivarlo. Difficile essere al 100%, li hai trovati tutti.
Konrad Morawski,

Prendi il meglio dei due mondi e scrivi una facciata con un gruppo di getter che invocano getCalculation().
nalply

1
Gli enum sono decisamente meglio delle stringhe! Non ci avevo pensato. Limitano l'API e impediscono l'abuso del parametro string (come il concat di token e altre schifezze) Quindi immagino che il confronto sia tra l'opzione 1 e l'opzione 2 con enum anziché stringa.
Bgie,

Risposte:


6

Penso che nel primo approccio pagheranno. Le stringhe magiche possono creare i seguenti problemi: errori di battitura, uso improprio, sicurezza del tipo di ritorno non banale, mancanza di completamento del codice, codice poco chiaro (questa versione aveva quella funzionalità? Immagino che lo scopriremo in fase di esecuzione). L'uso di enum risolverà alcuni di questi problemi, ma diamo un'occhiata ai contro che hai sollevato:

  • Il numero di getter aumenterà, poiché ogni nuovo calcolo che inventiamo viene aggiunto all'elenco

È vero, può essere fastidioso, tuttavia mantiene le cose belle e rigorose e ti dà il completamento del codice in qualsiasi parte del tuo progetto in qualsiasi IDE moderno, con buoni commenti di intestazione che sono molto più utili di enumerazioni.

  • È più probabile che l'API cambi e, se vengono introdotte modifiche, abbiamo bisogno di una nuova versione dell'API, Foot2.

È vero, ma in realtà è un grande pro;) è possibile definire interfacce per API parziali, quindi non è necessario ricompilare la classe dipendente che non è interessata dalle API più recenti (quindi non è necessario Foot2). Ciò consente un migliore disaccoppiamento, la dipendenza è ora dell'interfaccia e non dell'implementazione. Inoltre, se un'interfaccia esistente cambia, si verificherà un errore di compilazione nelle classi dipendenti, il che è ottimo per prevenire il codice non aggiornato.

  • in caso di riutilizzo della classe in altri progetti, potrebbe non essere necessario ogni calcolo

Non vedo come l'uso di stringhe magiche o enumerazioni ti aiuterà in questo ... Se ho capito bene, o includi il codice nella classe Foot o lo spezzi in alcune classi più piccole, e questo vale per entrambe le opzioni


Mi piace l'idea delle API parziali con le interfacce, sembra a prova di futuro. Andrò con quello. Grazie. Se dovesse diventare troppo disordinato (foot implementando troppe interfacce), l'uso di diverse classi di adattatori sarebbe ancora più flessibile: se esistessero diverse varianti di foot con API diverse (come foot, humanfoot, dogfoot, humanfootVersion2) potrebbe esserci un piccolo adattatore per ognuno per consentire a un widget della GUI di funzionare con tutti ...
Bgie,

Un vantaggio dell'utilizzo di un selettore di comandi è che si può avere un'implementazione che riceve un comando che non capisce chiamare un metodo di supporto statico fornito con l'interfaccia. Se il comando rappresenta qualcosa che può essere fatto con quasi tutte le implementazioni utilizzando un approccio di uso generale, ma che alcune implementazioni potrebbero essere in grado di fare con mezzi migliori [considerare ad esempio IEnumerable<T>.Count], un tale approccio può consentire al codice di godere dei vantaggi in termini di prestazioni di nuovi funzionalità dell'interfaccia quando si utilizzano implementazioni che li supportano, ma rimangono compatibili con le implementazioni precedenti.
supercat

12

Vorrei raccomandare l'opzione 3: chiarire che i calcoli non sono una parte intrinseca dell'astrazione di a Foot, ma operano su di essa. Quindi puoi dividere Foote i calcoli in classi separate, in questo modo:

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

In questo modo, hai ancora una digitazione forte del tuo calcolo e puoi aggiungerne di nuovi senza influire sul codice esistente (a meno che tu non abbia commesso un grave errore nella quantità di informazioni esposte da Foot).


Decisamente più bello delle opzioni 1 e 2. Questo è solo un passo dall'uso del modello di progettazione della strategia.
Brian,

4
Questo potrebbe essere appropriato. Questo potrebbe essere eccessivo. Dipende dalla complessità dei calcoli. Stai per aggiungere una MaxPressureFrameStrategy e una MaxPressureFrameStrategyFactory? E tende a trasformare il piede in un oggetto anemico.
user949300,

Sebbene questa sia una buona alternativa, invertire le dipendenze non funzionerebbe nel nostro caso. Il piede deve anche agire come una sorta di mediatore, poiché alcuni calcoli devono essere ricalcolati se un altro cambia (a causa della modifica da parte dell'utente di un parametro o giù di lì).
Bgie,

@Bgie: Con tali dipendenze tra i calcoli, concordo con @ user116462 che la prima opzione è la migliore.
Bart van Ingen Schenau,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.