Pattern da utilizzare (se esiste) per coordinare classi vagamente accoppiate con forti interdipendenze

2

Ho una collezione di classi cooperative i cui comportamenti sono interdipendenti l'uno con l'altro. Ma desidero mantenerli accoppiati liberamente, quindi ho creato interfacce appropriate.

Voglio determinare un modello appropriato per istanziare implementazioni specifiche di questi oggetti.

Ecco uno schema delle loro interdipendenze:

  • IService : IDisposable : ascolta i messaggi; espone un metodo Listen che:
    • chiama IMessageClient.GetNextMessage iterativamente
    • richiama un (delegato che crea una?) nuova istanza IMessageHandler in una nuova discussione per ogni messaggio
    • fino a NumberOfThreads di thread simultanei
  • IServiceMonitor<IService> : controlla il servizio:
    • espone il metodo Start che richiama IService.Listen()
    • espone il metodo Stop che dispone IService
    • espone i metodi Pause e Resume che rispettivamente azzerano o azzerano IService.NumberOfThreads
    • chiama CreateRemoteConfigClient() per ottenere un client ogni 90 secondi, quindi IRemoteConfigClient.GetConfig
    • notifica eventuali modifiche di configurazione a IMessageClient , IService e qualsiasi IMessageHandler successivo
  • %codice%; espone IMessageClient : IDisposable quale:
    • esegue il polling lungo una coda di messaggi per la richiesta successiva
  • %codice%; espone GetNextMessage quale:
    • fa qualcosa con il messaggio, richiedendo sulla strada ulteriori IMessageHandler : IDisposable s da HandleMessage per accedere ad altri servizi
  • %codice%; espone IXyzClient quale:
    • recupera qualsiasi override remoto dello stato di configurazione corrente

Questo mi ha portato a creare:

  • %codice%; con i seguenti membri:
    • IFactory : restituisce un nuovo IRemoteConfigClient : IDisposable
    • GetConfig : restituisce il IFactory creato per accompagnare il più recente CreateMonitor o un nuovo IServiceMonitor<IService>
      • NB: un servizio dovrebbe poter essere ottenuto senza che sia stato creato un monitor
    • GetService : restituisce un nuovo IService
    • In entrambi:
      • IServiceMonitor : restituisce un IService
      • CreateMessageClient : crea un IMessageClient e invoca CreateMessageHandler
    • new IMessageHandler : restituisce un MessageHandlerDelegate

Le implementazioni delle interfacce principali accettano new IMessageHandler nei loro costruttori. Questo è così:

  • HandleMessage può chiamare CreateRemoteConfigClient per ottenere un singolo new IRemoteConfigClient che sarà IFactory quando è fatto
  • IService può chiamare CreateMessageClient() per consentirgli di coordinare e monitorare IMessageClient
  • Dispose può riportare i suoi progressi indietro tramite IServiceMonitor

GetService() , ovviamente, è iniziato apparentemente come un'implementazione del pattern Factory, quindi ha iniziato a inclinarsi maggiormente verso un pattern Builder, ma in realtà nessuno di questi sembra giusto. Sono IService -ing alcuni oggetti, IMessageHandler -tando gli altri, e certe cose, come il fatto che una successiva chiamata a IMessageClient modificherà il risultato di IFactory , solo che non va bene.

Qual è la convenzione di denominazione giusta per una classe che coordina tutti questi altri, E c'è un modello effettivo che può essere seguito, sono troppo ingegnerizzato o sto sovra-analizzando?!

    
posta Arthur Sanchez 05.02.2016 - 02:17
fonte

3 risposte

0

Potresti provare il schema del mediatore . L'abbiamo usato un paio di volte.

Ad esempio:

// interface for all "Collegues" 
public interface IColleague
{
    Mediator Mediator { get; }
    void OnMessageNotification(MediatorMessages message, object args);
}
public enum MediatorMessages
{
    ChangeLocale,
    SetUIBusy,
    SetUIReady
}
public class Mediator
{
    private readonly MultiDictionary<MediatorMessages, IColleague> internalList =
        new MultiDictionary<MediatorMessages, IColleague>(EnumComparer<MediatorMessages>.Instance); //contains more than one value per key
    public void Register(IColleague colleague, IEnumerable<MediatorMessages> messages)
    {
        foreach (MediatorMessages message in messages)
            internalList.AddValue(message, colleague);
    }
    public void NotifyColleagues(MediatorMessages message, object args)
    {
        if (internalList.ContainsKey(message))
        {
            //forward the message to all listeners
            foreach (IColleague colleague in internalList[message])
                colleague.MessageNotification(message, args);
        }
    }

    public void NotifyColleagues(MediatorMessages message)
    {
        NotifyColleagues(message, null);
    }
}

E ora i colleghi (o controller nel nostro caso lo hanno implementato in questo modo):

public class AddInController : IColleague
{
    public Mediator Mediator
    {
        get { return mediatorInstance; }
    }

    public AddInController()
    {
        Mediator.Register(this, new[]
                                    {
                                        MediatorMessages.ChangeLocale,
                                        MediatorMessages.SetUIBusy,
                                        MediatorMessages.SetUIReady
                                    });
    }

    public void OnMessageNotification(MediatorMessages message, object args)
    {
        switch(message)
        {
            case MediatorMessages.ChangeLocale:
                UpdateUILocale();
                break;
            case MediatorMessages.SetUIBusy:
                SetBusyUI();
                break;
            case MediatorMessages.SetUIReady:
                SetReadyUI();
                break;
        }
    }
}
    
risposta data 12.02.2016 - 21:59
fonte
2
 I have a collection of cooperative classes whose behaviors are 
 interdependent upon one another. But I wish to keep them loosely coupled...

Attenta ora!

Quando stai pensando a quel pensiero esatto, è tempo di fare un passo indietro e guardare cosa stai cercando di ottenere e perché. L'accoppiamento lento è ottimo, ma non è un obiettivo a sé stante. Ogni volta che fai un accoppiamento più morbido, ottieni anche una minore coesione. La coesione è altrettanto importante per la manutenibilità di un codice base come accoppiamento (anche se tendiamo a parlarne meno).

Forse è difficile coordinare le interazioni tra queste classi perché sono troppo sciolte? Non sto dicendo che sia sicuro, ma è qualcosa che dovresti prendere in considerazione.

  • Tutte queste classi sono sempre utilizzate come una singola unità oppure a volte usi solo una o due di queste classi da qualche altra parte?
  • Realizzi effettivamente più implementazioni di ogni classe o stai semplicemente introducendo un accoppiamento più flessibile per semplificare il test delle unità o seguire le "migliori pratiche"?

Se rispondi si alla prima domanda e "solo singole implementazioni" alla seconda, potresti perdere un livello di astrazione. In questo caso, direi che questo intero sottosistema contenente questa raccolta di classi è in realtà la tua "unità", non ogni classe in sé. E in effetti potresti voler eliminare l'accoppiamento libero e trattare l'intero sistema come una singola unità invece di trattare ciascuna parte come un'unità in sé stessa.

Questo consiglio potrebbe essere sbagliato però. Ma trovo che quando mi pongo domande come quello che mi stai chiedendo, in genere finisco con il codice migliore quando scelgo l'alta coesione rispetto all'accoppiamento lento. Quindi, come al solito, pensa attentamente e determina cosa è meglio per te nella tua situazione.

    
risposta data 12.07.2016 - 09:53
fonte
1

A questo punto, tutto ciò che avete osservato è che in un'applicazione lato server, le classi hanno un'ampia varietà di requisiti di dipendenza, creazione, interazione e gestione dell'ambito. Alcune classi hanno un accoppiamento temporale tra di loro; alcune classi hanno bisogno di Lazy<> di tutte le loro dipendenze per ritardare le loro istanziazioni all'ultimo momento possibile. E i fili sono animali interessanti.

Se stai usando un ottimo framework di dipendenza da dipendenza C # che è in sviluppo attivo e ha una buona base di utenti attivi, dovrebbe essere in grado di gestire l'intera gamma di requisiti per tutte le tue classi. Ma prima devi essere molto sicuro di questi requisiti. L'uso delle impostazioni sbagliate introdurrà punti deboli nel software.

Se in effetti stai cercando di implementare il tuo framework di dipendenze per le dipendenze, è importante iniziare a leggere i documenti di progettazione di alcuni framework DI esistenti. Altrimenti avrai bisogno di molto più tempo per cercare gli approcci migliori.

La paura dell'ingegneria è meritata. Il risultato più probabile per la tua attuale base di codice è che sarà abbandonato da qualche tempo nel prossimo futuro, poiché è un prodotto del tuo processo di apprendimento. Con una conoscenza migliore, troverai presto utile ricominciare da una lavagna vuota (prendendo in prestito molti frammenti di codice, schemi e conoscenze).

Detto questo, l'uso di fabbrica o DI sembra essere necessario , perché alcune delle tue classi sono singleton per definizione - non può esserci che una sola istanza. Le classi che devono comunicare con questi singleton non devono essere istanziate, altrimenti potrebbero essere create più istanze.

Dopo aver provato per un po 'con il DI / Factory, potresti scoprire che l'accoppiamento libero potrebbe non essere la principale preoccupazione del tuo progetto . Potresti scoprire che queste altre preoccupazioni meritano più della tua attenzione:

  • Timeout di rete, perché la rete potrebbe essere scollegata in qualsiasi momento, o alcuni pacchetti potrebbero non arrivare mai
  • Messaggi duplicati
  • Messaggi non validi o non validi
  • Timeout di esecuzione locale o errore.
  • Esaurimento risorse locali
  • Tentativi
    • Backoff esponenziale per alcuni tipi di errori
  • Gestione aggraziata di errori sconosciuti o totalmente inattesi
    • Esempio: un processo viene arrestato all'interno di un debugger ma un altro processo è ancora in esecuzione
    • Esempio: l'unità di rete smette di rispondere

Potrebbe essere necessario che il software sia in grado di analizzare versioni precedenti dei formati dei messaggi, ma il timore di dover caricare diverse versioni di build degli assembly C # è a volte eccessivo. Puoi sempre ricostruire l'intero set di assembly C # sotto il tuo controllo in modo che siano sempre coerenti.

    
risposta data 12.07.2016 - 15:01
fonte