Quali sono i pattern più utilizzati per gestire molti parametri interconnessi?

7

Recentemente ho iniziato a lavorare su un'applicazione che supporta diversi dispositivi di misurazione.

Prima che l'utente inizi una misura, ne imposta i parametri.

In realtà, considerando tutti i tipi di misure ci sono oltre 50 parametri.

La difficoltà qui è che ogni impostazione dipende dagli altri per:

  • Essere disponibile
  • Elenco dei valori disponibili
  • così via ..

Inoltre, alcune impostazioni di misurazione dipendono dai risultati e dalle impostazioni precedenti della misura.

Per farla breve: abbiamo un sacco di cose che sono interconnesse.

Lo schema attuale è quello di convalidare tutto non appena viene modificato un valore. È costato molto (in tempo) e aggiungeremo molti più parametri: si romperà.

Cerchiamo di implementare un pattern in cui utilizziamo ObservableValues e in cui tutti i parametri si registrano su tutti i valori da cui dipende.

È diventato difficile quando i parametri dipendono da un'altra misura di riferimento. Se il cambiamento di riferimento, dobbiamo smettere di ascoltare sul riferimento precedente e iniziare ad ascoltare sul nuovo riferimento.

Etc ...

Un altro problema è che quando lavoriamo sul nostro pattern e abbiamo più capacità (come la serializzazione), o quando abbiamo una classe helper (come factory), costruiamo file di grandi dimensioni con oltre 50+ parametri o funzioni.

C'è qualche altro buon modello o libreria per farlo?

    
posta Orace 11.06.2015 - 09:33
fonte

6 risposte

1

Suggerirei:

  • dividi e vinci il problema
  • usa i componenti e mantieni ogni componente focalizzato sul suo scopo principale
  • disaccoppia il tuo sistema utilizzando un aggregatore di eventi (EA)
  • usa le interfacce in cui un EA non ha senso

Vorrei provare a dividere il grosso problema in problemi più piccoli e poi cercare di risolverli da solo. I problemi più piccoli possono essere risolti più facilmente con componenti specializzati, se sono abbastanza semplici. Ogni componente dovrebbe concentrarsi sul suo scopo principale.

  • Per mantenere un accoppiamento libero attorno ai componenti, utilizzerei un aggregatore di eventi (EA) (scopo principale: informare gli ascoltatori). (ad esempio: Reactive Extensions, Caliburn Micro o Prism EventAggregator, se in .NET)
  • Vorrei usare semplici classi di parametri per mantenere i valori (scopo principale: gestire i valori).
  • Vorrei raggruppare i parametri in un qualche tipo di classe dell'albero (scopo principale: fornire parametri).
  • L'albero deve essere compilato o aggiornato. Per questo vorrei utilizzare un componente del builder dell'albero (scopo principale: build / update tree).
  • Il tree-builder deve essere avvisato quando reagire - > Notifica EA
  • Alcuni utenti dell'albero devono ricevere una notifica dopo che l'albero è cambiato - > Notifica EA
  • ...

Se i componenti non sono accoppiati saldamente, il sistema può essere ridimensionato o modificato quando necessario. I.e: Se il processo di costruzione è troppo complesso, allora è possibile utilizzare più classi di costruzione di alberi invece una. Per ottenere questo risultato, verrà aggiunto un componente aggiuntivo, uno stabilimento di costruzione degli alberi. Reagirebbe sugli eventi di costruzione / aggiornamento degli alberi fornendo il costruttore di alberi adeguato.

    
risposta data 16.06.2015 - 13:10
fonte
3

Crea una classe Parametro (supponendo C #): class Parameter<T> { T Value; string Name; }

Quindi crea un grafico con i nodi contenenti i valori dei parametri e le dipendenze come riferimenti nello stesso grafico.

E poi fai tutte le operazioni sul grafico nel suo insieme, non direttamente sui parametri: %codice% o graph.SetValue(parameter, value)

    
risposta data 15.06.2015 - 11:38
fonte
1

Potresti considerare di usare un mediatore per disaccoppiare le dipendenze degli eventi dei parametri. Questo è talvolta noto come Aggregatore di eventi . Le tue singole classi di parametri sono cablate per pubblicare e iscriversi agli eventi direttamente tramite EA.

L'esempio seguente utilizza Reactive Extensions ma potresti implementare tu stesso una versione semplice utilizzando i callback per la sottoscrizione e la pubblicazione.

public class EventAggregator
{
    private Subject<Event> _events;
    public IObservable<Event> Events { get { return _events; } }

    public void Publish(Event evt)
    {
        _events.OnNext(evt);
    }
}

public class Parameter<T> : IDisposable
{
    private readonly EventAggregator _ea;
    public Parameter(EventAggregator ea, string name, T initialValue)
    {
        this._ea = ea;
        this._name = name;
        this._value = initialValue;
        this._subscription = ea.Events.Subscribe(evt => HandleEvent(evt));
    }

    private readonly string _name;
    public string Name { get { return this._name; } }

    private T _value;
    public T Value
    {
        get { return this._value; }
        set
        {
            this._value = value;
            this._ea.Publish(new ValueChangedEvent(this._name, this._value);
        }
    }

    private void HandleEvent(Event evt)
    {
        // decide what to do with an event here - change availability of the parameter etc
    }

    public void Dispose()
    {
        this._subscription.Dispose();
    }
}

Crea un singolo EventAggregator e passalo come dipendenza ai tuoi oggetti Parameter :

var eventAggregator = new EventAggregator();
var length = new Parameter<int>(eventAggregator, "Length", 50);
var width = new Parameter<int>(eventAggregator, "Width", 50);

// this will fire an event to the event aggregator which can be handled by other parameters
width.Value = 20; 

// this unsubscribes from the event aggregator
length.Dispose(); 
    
risposta data 15.06.2015 - 14:42
fonte
0

Ci sono strumenti per aiutarti a gestire la complessità; vedi ad esempio l'approccio Modello di funzionalità .

Tuttavia, se dovessi farlo, proverei ad adottare un approccio di programmazione dei vincoli (gli alberi delle caratteristiche sono fondamentalmente una notazione semplificata per i vincoli). Poiché stai utilizzando C #, ti indicherò Prolog.NET . Nota comunque che anche se ho già usato Prolog, non conosco Prolog.Net in particolare.

Nota: modificherai questa risposta più tardi con un esempio, se possibile (non ho molto tempo ora). A proposito, preferirei usare uno dei tuoi esempi, se potessi fornirne alcuni.

    
risposta data 15.06.2015 - 11:06
fonte
0

Un'ultima idea:

Mantieni tutti i tuoi parametri in una grande mappa come:

class Params
{
   Map<String, Object> map;

   /* fancy helper methods... */
}

Aggiungi un set di oggetti regola:

interface Rule
{
  /* returns "OK" or an error message
   * or use exceptions or whatever you fancy */
  void validate(Params params); 

  /* returns the list of params that will trigger the rule */
  String[] triggers();
}

class FancyRule implements Rule
{
  String validate(Params params) {
     if( params.get("foo") <= params.get("bar") )
        return "foo should be greater than bar";
     else
        return "OK";
  }
  String[] triggers() {
     return ["foo", "bar"];
  }
}

Come ultimo passaggio, devi solo unire le cose insieme:

class Params
{
   Map<String, Object> map;

   void setParam(key,value) {
      map.put(key,value); // + keep a backup if desired

      // now validate everything depending on it
      for( rule in rules having key as trigger):
           if( rule.validate(this) != "OK" )
                // ...
   }
}
    
risposta data 16.06.2015 - 14:02
fonte
0

Ci sono molti modi diversi di approcciare l'aggiornamento di tali parametri interconnessi; Ho una precedente esperienza con due: Ai fini di questa risposta li chiamerò l'approccio "centralizzato" e l'approccio "decentralizzato".

Nell'approccio centralizzato, hai una singola funzione "UpdateEverything" che devi fare in modo di invocare ogni volta che fai qualcosa nel tuo intero sistema che ha anche la minima possibilità di richiedere un aggiornamento. Il vantaggio dell'approccio centralizzato è che è semplice: tutto il codice che implementa l'aggiornamento è in un unico posto, quindi è possibile leggerlo per accertarsi che sia corretto. Gli svantaggi dell'approccio centralizzato sono che a) la funzione "UpdateEverything" di solito finisce per essere mostruosamente lunga, b) fa tutto l'aggiornamento ogni volta che viene invocato anche se solo alcune cose possono essere cambiate e quindi solo alcune le cose potrebbero davvero essere aggiornate, e c) tende ad essere invocata eccessivamente, così da ridurre le possibilità di perdere un aggiornamento, quindi, nel complesso, rappresenta un enorme sovraccarico.

L'approccio decentralizzato è ciò che hai già detto di aver iniziato a lavorare; è molto più coinvolto e in realtà richiede la costruzione di qualche framework prima che possa essere usato. In questo approccio, ogni parametro è in grado di attivare una notifica "modificata" e ogni parametro ha la conoscenza di ognuno dei parametri da cui dipende, in modo che possa registrarsi con essi per essere avvisati quando cambiano, e quindi ricalcolare il proprio valore, eventualmente attivando la propria notifica "modificata" a sua volta. L'approccio decentralizzato risolve tutti i problemi dell'approccio centralizzato, ma è più elaborato e quindi più costoso da implementare.

Alcuni punti da notare:

  1. Avrai bisogno di introdurre e utilizzare principalmente un'interfaccia "ReadonlyParameter", che ha un "GetValue ()" e un "RegisterForChangedEvent ()", ma non "SetValue ()", perché non rende senso di impostare il valore di un parametro che è costruito per ricalcolare il proprio valore in base ai valori di altri parametri. "read-only" in questo contesto non significa che il suo valore non possa mai cambiare, significa semplicemente che non è possibile modificare il suo valore, sebbene il suo valore possa ancora cambiare a causa di altri motivi, quindi ha senso registrarsi con esso in ordine per osservare tali cambiamenti.

  2. Per quei parametri che ricevono valori dall'esterno, potrebbe essere necessario introdurre una classe "StoringParameter" che "memorizza" (contiene) il proprio valore, implementando l'interfaccia "ReadonlyParameter" e offrendo anche "SetValue ( ) "metodo.

  3. Un parametro che sa come calcolare il proprio valore dovrà memorizzare tale valore, in modo che a) possa rispondere a "GetValue ()" senza ricalcoli costosi, e b) emetta solo un "Modificato" notifica quando cambia il suo valore memorizzato nella cache, in modo da eliminare gli aggiornamenti non necessari.

  4. Un parametro che sa come calcolare il proprio valore avrà generalmente un metodo RecomputeOwnValue() privato che viene richiamato a) ogni volta che uno dei parametri dipende dalle modifiche, b) una volta alla fine del proprio costruttore , in modo da calcolare il suo valore iniziale. Se esiste la possibilità che un parametro possa essere necessario interrompere a seconda di un particolare parametro e iniziare invece a dipendere da un altro parametro, sarà inoltre necessario richiamare RecomputeOwnValue() ogni volta che un parametro dipendente viene aggiunto / rimosso.

  5. È possibile creare un'intera algebra di parametri per le operazioni utilizzate di frequente. Ad esempio, è possibile introdurre le classi di parametri AND, OR, XOR, ecc., Che sanno come ricalcolare i propri valori come risultato di un'operazione logica sui valori di altri due parametri booleani da cui dipendono, è possibile introdurre parametri di funzionamento aritmetici classi, facendo addizione, sottrazione, moltiplicazione, divisione ecc. e così via.

  6. È possibile scrivere semplici funzioni di supporto che istanziano i parametri interconnessi in modo conveniente quanto la scrittura di espressioni. Quindi, potresti avere qualcosa di simile a questo:

    d = fob (a, fiddle (b, c)));

Dove fob() crea un oggetto FobParameter , e fiddle() crea un oggetto FiddleParameter , quindi l'espressione sopra indica che il parametro d è un new FobParameter() che succede a dipendere da due istanze di ReadonlyParameter , per il quale passiamo il parametro preesistente a e un new FiddleParameter() , che a sua volta dipende da altre due istanze di ReadonlyParameter , per cui passiamo i parametri preesistenti b e c .

    
risposta data 18.06.2015 - 14:03
fonte

Leggi altre domande sui tag