2009-11-12 12 views
10

Background: Ho alcune classi che implementano un pattern di progettazione soggetto/osservatore che ho reso thread-safe. Un subject notificherà il suo observers con una semplice chiamata di metodo observer->Notified(this) se lo observer è stato creato nello stesso thread della notifica. Ma se lo observer è stato costruito in un thread diverso, la notifica verrà registrata su un queue per essere elaborata in seguito dal thread che ha creato lo observer e quindi è possibile effettuare la chiamata al metodo semplice quando viene elaborato l'evento di notifica.Aiutami a rimuovere un Singleton: cercare un'alternativa

Quindi ... Ho una mappa che associa thread e code che vengono aggiornati quando thread e code sono costruiti e distrutti. Questa mappa utilizza un mutex per proteggere l'accesso multi-thread ad esso.

La mappa è un singleton.

Sono stato colpevole di usare singoletti in passato perché "ce ne sarà solo uno in questa applicazione" e, credetemi, ho pagato la mia penitenza!

Una parte di me non può fare a meno di pensare che in un'applicazione ci sarà solo una mappa code/thread. L'altra voce dice che i singleton non sono buoni e dovresti evitarli.

Mi piace l'idea di rimuovere il singleton e di poterlo mozzare per i miei test di unità. Il problema è che sto attraversando un periodo difficile cercando di pensare a una buona soluzione alternativa.

La "solita" soluzione che ha funzionato in passato consiste nel passare un puntatore all'oggetto da utilizzare invece di fare riferimento al singleton. Penso che sarebbe difficile in questo caso, dal momento che osservatori e soggetti sono 10 penny nella mia applicazione e sarebbe molto imbarazzante dover passare un oggetto mappa coda/thread nel costruttore di ogni singolo osservatore.

Ciò che apprezzo è che potrei avere una sola mappa nella mia applicazione, ma non dovrebbe essere nelle viscere del codice della classe oggetto e osservatore in cui tale decisione è presa.

Forse questo è un singleton valido, ma apprezzerei anche qualsiasi idea su come potrei rimuoverlo.

Grazie.

PS. Ho letto What's Alternative to Singleton e this article menzionato nella risposta accettata. Non posso fare a meno di pensare che ApplicationFactory sia solo un altro singleton con un altro nome. Io davvero non vedo il vantaggio.

+2

Perché vuoi evitare i singleton? Hanno sicuramente il loro posto. Ogni linguaggio può essere usato e abusato. Ma una mappa a livello di applicazione di thread-> notification_queue mi sembra ragionevole. – Mordachai

+0

@Mordachai: Capisco single hanno il loro posto e può ben essere che questo code/thread è perfettamente valida. E 'iniziato solo a me bug quando stavo scrivendo alcuni test di unità ed è appena sentito scomodo avere il singleton lì. –

+0

Che libreria di thread stai usando? – outis

risposta

0

Gli osservatori possono essere economici, ma dipendono dalla mappa della coda di notifica, giusto?

Cosa c'è di strano nel rendere esplicita tale dipendenza e assumerne il controllo?

Per quanto riguarda la fabbrica di applicazioni che Miško Hevery descrive nel suo articolo, i maggiori vantaggi sono che 1) l'approccio di fabbrica non nasconde le dipendenze e 2) le singole istanze da cui si dipende non sono globalmente disponibili in modo tale che qualsiasi altro oggetto può interferire con il loro stato. Quindi, utilizzando questo approccio, in qualsiasi contesto applicativo di primo livello, sai esattamente cosa sta usando la tua mappa. Con un singleton globalmente accessibile, qualsiasi classe che usi potrebbe fare cose sgradevoli con o sulla mappa.

+0

L'intento era (è) che il meccanismo di coda/thread sia trasparente per gli utenti degli osservatori - tutto ciò che un osservatore derivato deve sapere è che il metodo Notified() non è necessariamente una chiamata di metodo sincrono se il threading è coinvolto. La classe base dell'osservatore può dipendere dalla mappa e da una coda di notifica, ma non voglio (se posso aiutarla) trascinare tale dipendenza in classi derivate e forzare ogni osservatore derivato a conoscere la mappa coda/thread. Ci sono molti (> 500) osservatori nella mia app! (Presumo che sia ciò che intendi per "prenderne il controllo"? –

+0

In effetti, questo è quello che intendevo! Non intendo portare la tua pazienza o predicare al coro qui, dato che hai già indicato che non come i singleton - ma la trasparenza dei singleton è solo illusoria, cioè è trasparente solo fino a quando fallisce. Detto questo, forse l'idea di JS Bangs è più gradevole se hai 500 classi di osservatori derivati ​​(yikes) –

+0

@ Jeff: questo non è necessariamente più di 500 osservatori per argomento :-) Ci sono molti osservatori e molti soggetti, in vari gradi di relazioni molti-a-molti –

1

Alcuni pensieri verso una soluzione:

Perché avete bisogno di accodare le notifiche per gli osservatori che sono stati creati su un thread diverso?Il mio progetto preferito sarebbe quello di fare in modo che lo subject notifichi direttamente gli osservatori e metta l'onere sugli osservatori per implementare i thread in sicurezza, con la consapevolezza che Notified() potrebbe essere chiamato in qualsiasi momento da un altro thread. Gli osservatori sanno quali parti del loro stato devono essere protette con serrature e possono gestirle meglio di subject o queue.

Supponendo che si abbia davvero una buona ragione per mantenere lo queue, perché non renderlo un'istanza? Basta fare queue = new Queue() da qualche parte in main e quindi passare intorno a tale riferimento. Può essercene solo uno, ma è comunque possibile considerarlo come un'istanza e non come una statica globale.

+0

Personalmente, quando scrivo osservatori multithread, preferisco che i miei metodi osservatori vengano sempre chiamati nel contesto del loro thread. Avere inietta un altro thread sembra utile solo nei casi in cui l'attività di obesità è molto, molto lite. – Mordachai

+0

@JS Bangs: Riesco a vedere cosa si sta dicendo riguardo la sicurezza dei thread, ma l'implementazione attuale prevede che i soggetti si occupino di mutex e sicurezza del thread piuttosto che degli osservatori. Cambiare quello adesso non andrebbe troppo bene con la squadra! (Per inciso, è la mappa del thread della coda che è il singleton, non una coda: ci sono molte code e thread, una mappa). Ciò che mi preoccuperebbe sono le modifiche necessarie per passare questo riferimento all'oggetto mappa ad ogni singola istanza di osservatore. –

+0

@Mordacahai: sì, questo è l'approccio che ho seguito: il metodo Notified() di ogni osservatore viene eseguito nel contesto del thread proprietario. Esistono meccanismi per forzare una notifica asincrona (accodata) (nel caso a thread singolo) o forzare una notifica sincrona (chiamata al metodo) (nel caso multithreading) ma sono usati raramente e sono per uso 'avanzato' se si è consapevole delle conseguenze. –

1

Cosa c'è di sbagliato nel mettere la coda nella classe oggetto? A cosa serve la mappa?

Hai già una lettura di thread dalla mappa coda singleton. Invece di fare che semplicemente rendono la mappa all'interno della classe soggetto e fornire due metodi di sottoscrivere un osservatore:

class Subject 
{ 
    // Assume is threadsafe and all 
    private QueueMap queue; 
    void Subscribe(NotifyCallback, ThreadId) 
    { 
    // If it was created from another thread add to the map 
    if (ThreadId != This.ThreadId) 
     queue[ThreadId].Add(NotifyCallback); 
    } 

    public NotifyCallBack GetNext() 
    { 
    return queue[CallerThread.Id].Pop; 
    } 
} 

Ora, qualsiasi thread può chiamare il metodo GetNext per iniziare dispacciamento ... ovviamente è tutto eccessivamente semplificata, ma è solo l'idea.

Nota: sto lavorando con il presupposto che si dispone già di un'architettura intorno a questo modello in modo che si dispone già di un gruppo di osservatori, uno o più soggetti e che i fili già andate alla mappa per fare il notifiche. Questo elimina il singleton ma ti suggerisco di notificare dallo stesso thread e lasciare che gli osservatori gestiscano i problemi di concorrenza.

+0

C'è una coda per thread. Ci possono essere molti osservatori costruiti in un thread. Inoltre, molti soggetti possono notificare dallo stesso thread, quindi avere una coda per argomento è eccessivo. –

0

Il mio approccio era quello di fare in modo che gli osservatori mettessero una coda quando si registravano con il soggetto; proprietario dell'osservatore sarebbe responsabile sia per il filo e la coda associata, e il soggetto avrebbe associare l'osservatore con la coda, senza la necessità di un registro centrale.

Gli osservatori thread-safe potevano registrarsi senza coda e essere chiamati direttamente dal soggetto.

+0

Capisco cosa intendi, ma penso che questo esponga troppe dipendenze. Uno dei vantaggi di nascondere il comportamento della coda da osservatori e soggetti derivati ​​è che diventa molto facile spostare osservatori tra i thread (nel codice sorgente, non nel runtime) e farli 'automaticamente' sapere quale coda usare; gli utenti degli osservatori e dei soggetti non devono preoccuparsi di questi dettagli di implementazione. –

+0

In tal caso, un singleton è probabilmente quello che vuoi. Non c'è alcun problema concettuale con questo, poiché per definizione esiste solo un "insieme di tutti i thread nel processo". In alternativa (ma meno portabile) è possibile inserire la coda nella memoria locale del thread se la propria piattaforma ha una cosa del genere. –

3

Se l'unico scopo di cercare di liberarsi del Singleton è dal punto di vista test di unità, forse sostituendo il getter Singleton con qualcosa che è possibile scambiare in uno stub per.

class QueueThreadMapBase 
{ 
    //virtual functions 
}; 

class QeueueThreadMap : public QueueThreadMapBase 
{ 
    //your real implementation 
}; 

class QeueueThreadMapTestStub : public QueueThreadMapBase 
{ 
    //your test implementation 
}; 

static QueueThreadMapBase* pGlobalInstance = new QeueueThreadMap; 

QueueThreadMapBase* getInstance() 
{ 
    return pGlobalInstance; 
} 

void setInstance(QueueThreadMapBase* pNew) 
{ 
    pGlobalInstance = pNew 
} 

Poi, nel tuo test appena scambiare l'attuazione di code/thread. Per lo meno questo espone il singleton un po 'di più.

+0

Ah ... interessante. In effetti, ho già scisso alcuni altri singleton in questo modo per i test, quindi posso solo riutilizzare la parte Base localmente e ignorare la parte singleton, ma non ho aggiunto il metodo 'setInstance'. Grazie. –

0

Che dire di aggiungere un metodo di ripristino che restituisce un singleton al suo stato iniziale che è possibile chiamare tra i test? Potrebbe essere più semplice di uno stub. Potrebbe essere possibile aggiungere un metodo di ripristino generico a un modello Singleton (elimina il pimpl Singleton interno e reimposta il puntatore). Questo potrebbe anche includere un registro di tutti i single con un metodo maestro ResetAll per ripristinare tutti loro!

+0

È qualcosa su cui vale la pena riflettere. Potrei combinarlo con l'idea del singleton "plug-in" di Snazzer. Saluti. –

Problemi correlati