Classe che restituisce più valori non correlati, esiste un modo migliore per ottenere questo risultato?

7

Ho una classe "tracker". Questa classe tiene traccia degli oggetti da una determinata immagine di input. Ma oltre a questo, c'è un altro parametro che corrisponde a un valore utilizzato nella generazione dell'immagine. Il tracker potrebbe voler suggerire un nuovo valore per questo parametro per ottenere risultati migliori alla successiva iterazione. Suggerirebbe questo nuovo valore per ciò che mai genera l'immagine.

Attualmente lo implemento tramite:

struct TrackResults{
    double new_scene_generation_coefficient;
    std::vector<Track> tracks;
}

class Tracker{
public:
    TrackResults update(const Image& image, double scene_generation_coefficient){
        // generate tracks and a new_scene_generation_coefficient 
        // ...
        return {new_scene_generation_coefficient, tracks}
    }
}

La mia intuizione è che non vogliamo modificare direttamente il valore e vogliamo evitare che il Tracker dipenda dalla conoscenza di una classe di cui ha solo bisogno di un valore e che potrebbe potenzialmente cambiare. Sento che se ritornassi per riferimento qui, potrei potenzialmente causare un effetto a cascata di qualsiasi cosa che usi il tracker per dipendere da qualcosa che altrimenti non avrebbe bisogno, o passare pericolosamente intorno a un riferimento mutabile.

Non trovo alcun problema in termini di manutenibilità con questo, ma ci sono alcuni problemi ergonomici molto lievi, per esempio, quando voglio le tracce, devo prima passare attraverso TrackResult oggetto.

Tuttavia sono preoccupato, dato che sembra un odore di codice, i valori nella classe dati non hanno nulla a che fare l'uno con l'altro e non ci sono metodi che userebbero entrambi in tandem. In altri post le persone sostengono che non ci dovrebbero essere classi di dati, dal momento che dovresti essere in grado di spostare le funzioni che lavorano sui membri della classe in un metodo.

Un'altra cosa che dovrei menzionare, è che le tracce vengono mantenute internamente in ogni modo. Le tracce vengono restituite in base al valore per evitare di interferire con la struttura interna della classe da parte di attori esterni (come i logger che esistono nel sistema reale). Questo mi fa pensare che le tracce dovrebbero essere interrogate dopo update , e new_scene_generation_coefficient è l'unica cosa che dovrebbe essere restituita. Potenzialmente, tuttavia, ci sono più valori che dovrebbero essere usati per modificare l'oggetto che ha generato Image in primo luogo, ma almeno questi sono correlati. Un problema con la query di traccia separata dopo l'aggiornamento è che diventa pericoloso per l'utente, in quanto non vi è alcuna garanzia che proveranno a eseguire query direttamente in seguito e che anche se lo fanno, otterranno lo stato dell'elenco di tracce come era quando veniva chiamato update() . Questo sembra un odore di codice per richiedere a un utente di chiamare prima un altro metodo per utilizzare in questo modo un metodo separato.

    
posta opa 29.11.2018 - 22:46
fonte

2 risposte

3

Sembra che questo possa essere pulito facilmente. Hai una procedura che impara sulle iterazioni. Ogni volta che viene chiamato, la qualità di un parametro diventa leggermente migliore. La prima volta che viene chiamata la procedura, è necessario un po 'di figure iniziali per iniziare.

Quindi, potresti passare initial_scene_generation_coefficient al costruttore di Tracker e memorizzarlo come valore membro scene_generation_coefficient. Rimuovi l'argomento dall'elenco degli argomenti di Update e usa invece il membro, che aggiorni su ogni chiamata.

Il coefficiente di generazione è privato per il Tracker, non c'è motivo di renderlo disponibile al di fuori di esso oltre a soddisfare la tua curiosità (debugging).

Dovrai bloccare il codice in Update se vuoi renderlo thread-safe.

[Modifica]

OK, ho capito subito dopo il tuo commento. L'errore che vedo è che il tracker dovrebbe avere una comprensione della generazione di immagini. Questo non ha senso e viola SRP. Il tracker dovrebbe riconoscere gli oggetti nell'immagine. Non è in alcun modo possibile valutare la qualità dell'immagine, che richiede un occhio umano o un numero di iterazioni che può produrre un numero diverso di oggetti riconosciuti. Se escludiamo i falsi positivi, potresti dire che più oggetti riconosciuti ottieni, migliore deve essere stata la qualità dell'immagine. Sembra che tu voglia alimentare il tracker una serie di immagini generate con condizioni di illuminazione diverse e combinare i risultati del tracciamento. Non ottenendo risultati coerenti con alcune immagini, è possibile che non si diano più fastidio a generarle (per utilizzare tale coefficiente di generazione perché non è redditizio). Ma il coefficiente non ha senso nel contesto del tracker, quindi non lo si passerebbe o non lo si recupererebbe.

Recupera gli oggetti riconosciuti e puoi provare le immagini appena generate per vedere se riesci a recuperare più oggetti dal tracker. Potresti nutrirlo con immagini più scure e chiare e vedere se produce più oggetti. Questa è la direzione in cui entrare con il tuo coefficiente fino a quando non ottieni di nuovo meno oggetti. Allora avrai superato il tuo optimum.

    
risposta data 02.12.2018 - 08:32
fonte
0

Prima di tutto penso che l'idea di "non correlato" qui sia un po 'confusa se è necessario recuperare e passare costantemente quel parametro per ottenere gli ultimi risultati. Non so esattamente cosa scene_generation_coefficient dovrebbe fare tranne che sto vedendo che fondamentalmente stai facendo in modo che il client tenga traccia di quello stato e lo passi costantemente nell'oggetto, ottieni un risultato, solo per passalo nuovamente e ripeti.

Questo è fondamentalmente esternalizzazione della responsabilità della gestione dello stato verso il cliente (la persona che usa il tuo Tracker ). E ci sono ragioni legittime per farlo in alcuni casi, ma penso che la maggior parte delle ragioni legittime siano in contesti più procedurali o funzionali.

Se utilizziamo OOP, uno dei punti chiave degli oggetti quando serve un'esigenza pratica è evitare questo genere di cose. Pertanto, un progetto alternativo consiste semplicemente nel rendere il Tracker di memorizzazione e di aggiornamento del coefficiente direttamente, e istanziare più tracker se è necessario utilizzare questi oggetti in contesti eterogenei. Se vuoi riutilizzare lo stesso tracker mentre i coefficienti completamente disparati vengono utilizzati contemporaneamente in diverse funzioni o thread di chiamata contemporaneamente, forse potresti rendere TrackerResults responsabile per questo:

class TrackerResults
{
public:
    TrackerResults(...) {}

    // Even the image parameter might be a member passed through ctor
    // depending on design requirements.
    void update(const Image& image)
    {
        *this = tracker->update(image, scene_generation_coefficient);
    }

    const Track& operator[](size_t n) const 
    {
         return tracks[n];
    }

private:
    // Can use something like shared_ptr for robustness alternatively.
    Tracker* tracker;

    // Manage this internally in the object itself so the client doesn't
    // have to constantly retrieve and pass it back in.
    double scene_generation_coefficient;

    std::vector<Track> tracks;
};

... qualcosa del genere per un'illustrazione grezza. Quindi l'utente costruisce uno o più di questi oggetti TrackerResults e chiama ripetutamente update per continuare a ottenere un risultato migliore senza dover gestire manualmente questo stato dei coefficienti. Qualcosa come questo. Se il cliente è libero di ignorare il "suggerimento" per un nuovo coefficiente, potrebbe voler monitorare il coefficiente esternamente e potresti programmare quella caratteristica in TrackResults con un sovraccarico, ma soprattutto proverei a farlo il client non deve gestire tale stato in modo tale da richiedere costantemente il passaggio del coefficiente tramite parametro e l'acquisizione del coefficiente risultante.

C'è un atto di bilanciamento qui e non è sempre così semplice, ma una regola pratica per iniziare prima di violare è non fare in modo che il client gestisca più stato di quello che deve quando si tratta di OOP. Non si desidera progettare una classe contenitore che richiede al client di mantenere la dimensione del contenitore esternamente alla classe stessa, dovendo costantemente passarla e recuperare la nuova dimensione in qualsiasi funzione che possa modificare la dimensione del contenitore come esempio lampante ed estremo.

Riesco a comprendere le preoccupazioni di evitare gli effetti collaterali qui, ma con gli oggetti la convenienza deriva dall'incapsulare il tipo di stato per il quale gli effetti collaterali sono inevitabili. Per evitare sovrapposizioni / sovrascritture e condizioni di gara, si finisce per utilizzare più di un oggetto di quel tipo nello stesso modo in cui non si usa una variabile intera per catturare ogni singolo numero intero.

An issue with the separate track query after update is that it becomes dangerous for the user, as there is no guarantee that they will try to query directly afterwards, and that even if they do, that they will get the state of the track list as it was when update() was called.

Vado avanti ed elencherò un'altra preoccupazione nella stessa linea di pensiero, ovvero che lo stato condiviso del coefficiente renderebbe il tracker non più protetto da thread senza blocco. Ma questa è una preoccupazione facilmente risolvibile usando più istanze di oggetti con ogni istanza che mantiene il proprio coefficiente separato. Questo è il modo OOP in cui questi tipi di funzioni spesso incorrono in un singolo effetto collaterale, ma la classe facilita il mantenimento degli invarianti e così via. Puoi anche creare un blocco attorno a questo senza modificare alcun codice client se hai bisogno di utilizzare lo stesso oggetto come stato condiviso per qualsiasi motivo.

I don't find any issues in terms of maintainability with this, but there are some very slight ergonomic issues, for example, when I want tracks, I have to first go through TrackResult object.

Blargh, l'ergonomia è dannata in un linguaggio come il C ++. Cercare di fare cose come minimizzare troppo il boilerplate è generalmente controproducente qui per favorire un codice più diretto (se è l'alternativa) che è meno probabile che venga frainteso. Tuttavia, se andiamo con le risposte proposte, otteniamo sia una soluzione più ergonomica che una non troppo incline all'abuso, a condizione che il cliente comprenda che il coefficiente di scena è parte dello stato dell'oggetto che viene aggiornato con ogni chiamata di update .

    
risposta data 03.12.2018 - 11:13
fonte

Leggi altre domande sui tag