Come devono essere collegati i nodi di una catena?

0

Supponi di creare un sistema che riceve dati da una parte e manda i dati filtrati dall'altra parte.

Il sistema è una catena di nodi, ognuno dei quali riceve prima i dati dal nodo e invia i dati filtrati al successivo.

Hai due approcci da scegliere riguardo al modo in cui i nodi verrebbero collegati:

A- Ogni nodo contiene un riferimento al successivo attraverso una particolare interfaccia.

B- Ogni nodo espone un evento e il successivo nodo in linea verrebbe registrato per quell'evento, notificato quando l'evento è attivato (quando il nodo precedente nella riga invia i dati).

In entrambi i casi, un'entità esterna collega i nodi l'uno con l'altro; non hanno conoscenza diretta l'uno dell'altro.

Attualmente, penso che gli eventi potrebbero essere migliori perché in questo modo, a un nodo non interessa cosa fa il nodo successivo; solo ciò che deve inviarlo. In tal modo ci consente di inserire facilmente i nodi nel mezzo.

C'è una possibilità che i nodi dovrebbero essere inseriti nel mezzo, in futuro.

Tenendo conto di ciò, quale approccio preferiresti e perché? Quali sono i pro e i contro di ciascun approccio?

    
posta Aviv Cohn 15.01.2015 - 23:15
fonte

4 risposte

4

Dipende dalla stratificazione del design. Se i "nodi" sono concettualmente tutti nello stesso livello, allora preferirei riferimenti su eventi, in particolare riferimenti di sola lettura. I riferimenti sono più semplici e facili da tracciare con gli strumenti di analisi statici:

  • I riferimenti possono essere campi di sola lettura, mentre i campi evento sono sempre modificabili.
  • Un campo di riferimento si riferisce a una singola istanza di oggetto, mentre i campi di evento contengono un elenco collegato di delegati.
  • Un campo di riferimento è strongmente digitato. Un campo evento è debolmente digitato, poiché qualsiasi metodo può essere collegato all'evento. Una digitazione più strong può ridurre gli errori, come si verificano in fase di compilazione, e rende più semplice la ricerca o l'analisi dell'output del compilatore utilizzando gli strumenti di analisi.

Se alcuni nodi si trovano in uno strato di livello inferiore, utilizzerei gli eventi per la comunicazione dal livello inferiore al livello superiore, per evitare riferimenti da un livello inferiore a uno superiore.

La tua domanda è vaga e non ci sono abbastanza informazioni per formulare raccomandazioni specifiche.

    
risposta data 15.01.2015 - 23:25
fonte
2

Identificazione del modello.

Questo modello è in realtà piuttosto intelligente. In effetti è così utile che Microsoft abbia scritto una libreria considerevole e ben gradita chiamata Estensioni reattive per risolvere questo problema utilizzando una tecnica chiamata programmazione reattiva funzionale . Il pattern che stai cercando di nominare è chiamato Observable .

Generalizza fondamentalmente un IEnumerable . Hai una collezione di elementi che ti piacerebbe steam attraverso una serie di trasformazioni come mappatura, aggregazione di filtri e così via. Tuttavia, anche tu hai un Task fondamentalmente poiché hai un'azione dipendente dal tempo che non è immediatamente disponibile.

Fondamentalmente abbiamo un grafico che va in questo modo:

          |   Singular    | Plural
------------------------------------------------
Spatial   |   Value           | Enumerable<Value>
Temporal  |   Task<Value>     | Observable<Value>

Concettualmente un osservabile è ciò che modella il tuo problema . Ci sono state molte ricerche su questo e dovresti davvero evitare di inventare la ruota, se possibile. Il flusso di utilizzo di un osservabile è qualcosa del genere:

Il flusso di lavoro quando si utilizza un osservabile

Link a esempi reali più tardi.

  • Si inizia con una fonte , ad esempio un timer o un flusso di pacchetti di dati, una funzione che esegue una richiesta ogni pochi secondi o una serie di back-to-back e così via.
  • Quindi, le trasformazioni vengono applicate ad ogni turno - puoi prendere un osservabile esistente e .Select in un nuovo osservabile mappato o .Where in un filtro. Puoi anche rimuoverlo, flatMap ed eseguire trasformazioni interessanti. Questo è come LINQ solo LINQ rappresenta una singola query differita una volta in cui un Observable rappresenta un processo in corso.
  • Puoi scartare l'osservabile iscrivendoti ad esso, eseguendo una funzione alla sua fine.
  • Puoi eseguire il fork di un osservabile creando non solo "elenchi di nodi" ma interi alberi.

Questo concetto di osservabile funzionale che le catene non è solo più potente degli eventi e si aggrega bene (puoi unire osservabili, testarli facilmente e così via) - è anche molto semplice. Il tuo codice può essere simile a:

GetDataItems() // returns a new observable
.Where(IWantItemsForTheNextRound) // filtering logic
.Select(Mapper) // mapping logic
... // any more transformations

Proprio come lavorare con IQueryable in LINQ, le vecchie trasformazioni non interessano e non sono a conoscenza di ciò che è passato da quel momento in poi. Come LINQ hai una strong astrazione sull'elaborazione di una collezione, solo che questa volta è necessario del tempo.

Puoi inserire un nodo nel mezzo mettendo un gancio per esso e iniettando una funzione che fa qualcosa con quel gancio - se ti appiattisci su quella mappa puoi mettere la logica arbitrariamente complicata là estendendola quanto vuoi.

Teoria e lettura attuali.

Vi è abbondanza di materiale teorico sul perché questo approccio sia molto efficace in gor di KrisKowal o in Dualità e la fine del reattivo .

risposta data 17.01.2015 - 21:01
fonte
0

In una singola applicazione i riferimenti sono più facili. Soprattutto, ti permetteranno di elaborare i dati in modo asincrono.

Se i tuoi nodi possono trovarsi in diverse applicazioni (anche in computer diversi) avrai bisogno di un modello basato sugli eventi come la Coda messaggi. Questo modello può essere implementato con l'aiuto di eventi .NET in ogni singola applicazione.

Quindi, se non hai intenzione di ridimensionare l'applicazione, è più semplice utilizzare i riferimenti anziché gli eventi.

    
risposta data 16.01.2015 - 08:46
fonte
0

Questa è una domanda complicata :) Se davvero dovessi scegliere tra solo queste due opzioni, sceglierei A per la forma più semplice del tuo compito e B per quella estesa. Questo perché gli eventi non hanno senso finché non si chiama esattamente una funzione.

La responsabilità principale dei tuoi nodi è quella di filtrare i dati che ricevono. Ancora quando cambi la struttura del tuo grafico, devi anche cambiare la classe del tuo nodo, anche se la modifica non ha nulla a che fare con la sua funzione primaria.

Il modo per aggirare questo è conformarsi al Principio di Responsabilità Unica (SRP) . Assegna ai nodi la responsabilità esclusiva del filtraggio e crea una classe che trasferisce i dati tra i nodi.

Quindi ecco l'opzione C:

interface InputNode
{
  DataBuffer getData();
}

interface OutputNode
{
  void setData(DataBuffer data);
}

class FilterNode : InputNode, OutputNode
{
  ...
}

Implementa questo nodo in modo che la sua unica responsabilità sia quella di eseguire il filtro . Rendilo assolutamente inconsapevole di tutti gli altri nodi! E questa è l'interfaccia della classe che trasferisce i dati tra i nodi:

interface DataTransfer
{
  InputNode {get; set;}
  OututNode {get; set;}
  void transfer();
}

E questo è il modo in cui dovrebbe apparire il tuo grafico:

class Graph
{
  private List<DataTransfer> transfers;

  public Graph()
  {
    //initialize nodes and transfers
    ...
  }

  public void setData(DataBuffer data)
  {
    transfers[0].setData(data);
  }

  public void process()
  {
    foreach(var transfer in transfers)
      transfer.transfer();
  }

  public DataBuffer getData()
  {
    return transfers.Last().getData();
  }
}

Se vuoi estendere questa soluzione come hai descritto, dovrai modificare l'interfaccia DataTransfer per avere un elenco di nodi di output e dovrai modificare la rappresentazione del grafico in modo che possa eseguire la ramificazione.

    
risposta data 16.01.2015 - 13:50
fonte

Leggi altre domande sui tag