I migliori modelli per articoli usa e getta per scope variabili

2

Ho Client che utilizza un Connection usa e getta per parlare con un servizio remoto. Un Connection è alquanto costoso da configurare e deve essere Dispose () d correttamente.

Voglio consentire più metodi in Client per condividere una connessione, come questa:

using (client._connection = client.Connect())
{
  client.SyncTime();
  client.UpdateUsers();
  client.SomeOtherOperation();
}

Ma non voglio insistere sui consumatori di Client che gestiscono le connessioni. Dovrebbero essere autorizzati a chiamare singoli singoli metodi Client senza essere a conoscenza o preoccupati di Connection s.

Ecco lo schema che sto usando al momento. Fa piangere SRP. Qualche suggerimento su come posso fare meglio, o dovrei farlo affatto?

public class Client
{
  Connection _connection;

  public void SyncTime()
  {
    bool wasConnected = _connection == null;
    try
    {
      if (!wasConnected)
        _connection = Connect();

      _connection.ActuallyDoSomeWork();
      ...

    }
    finally
    {
      if (!wasConnected)
        _connection.Dispose()
    }
  }
}
    
posta daveharnett 18.07.2014 - 19:56
fonte

2 risposte

1

Puoi racchiudere il% co_de monouso in un involucro monouso conteggiato con riferimento, in questo modo:

public abstract class RefCountDisposable<TDisposable> : IDisposable where TDisposable : class, IDisposable
{
    TDisposable reference;

    protected RefCountDisposable(TDisposable reference)
    {
        this.reference = reference;
    }

    public TDisposable Reference { get { return reference; } private set { reference = value; } }

    public bool IsDisposed { get { return reference == null; } }

    public void Dispose()
    {
        // Dispose of unmanaged resources.
        Dispose(true);
        // Suppress finalization.  Since this class actually has no finalizer, this does nothing.
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (IsDisposed)
            return;
        if (disposing)
        {
            // Free any other managed objects here. 
            Reference = null;
        }
        // Free any unmanaged objects here. 
    }

    public override string ToString()
    {
        if (IsDisposed)
            return base.ToString() + ": Disposed";
        else
            return base.ToString() + ": " + reference.ToString();
    }
}

public sealed class RefCountDisposableFactory<TDisposable> where TDisposable : class, IDisposable
{
    Func<TDisposable> getReference;
    TDisposable reference;
    int refCount;
    object padlock = new object();

    sealed class RefCountDisposable : RefCountDisposable<TDisposable>
    {
        readonly RefCountDisposableFactory<TDisposable> factory;

        internal RefCountDisposable(RefCountDisposableFactory<TDisposable> factory, TDisposable reference)
            : base(reference)
        {
            if (factory == null)
                throw new ArgumentNullException("factory");
            this.factory = factory;
        }

        protected override void Dispose(bool disposing)
        {
            if (IsDisposed)
                return;
            try
            {
                if (disposing)
                {
                    // Free any other managed objects here. 
                    factory.DisposeReference(Reference);
                }
                // Free any unmanaged objects here. 
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
    }

    public RefCountDisposableFactory(Func<TDisposable> getReference)
    {
        this.getReference = getReference;
        this.reference = null;
        this.refCount = 0;
    }

    public RefCountDisposable<TDisposable> Create()
    {
        lock (padlock)
        {
            if (reference == null)
                reference = getReference();
            refCount++;
        }

        return new RefCountDisposable(this, reference);
    }

    void DisposeReference(TDisposable reference)
    {
        if (reference == null)
            return; // already disposed.
        lock (padlock)
        {
            if (reference != this.reference)
            {
                string msg = string.Format("invalid reference {0}", reference);
                Debug.Assert(false, msg);
                throw new ArgumentException(msg);
            }
            if (refCount <= 1)
            {
                if (reference != null)
                    reference.Dispose();
                reference = null;
                refCount = 0;
            }
            else
            {
                refCount--;
            }
        }
    }
}

L'idea è che Connection assegna un RefCountDisposableFactory la prima volta che ti serve e lo restituisce in un Connection wrapper. Le richieste successive di un RefCountDisposable<Connection> incrementano un conteggio dei riferimenti e restituiscono nuovi wrapper allo stesso riferimento Connection . Una volta eliminato l'ultimo wrapper, Connection viene eliminato e impostato su null in modo che le richieste successive creino una nuova istanza.

Il mio prototipo di fabbrica non è robusto nei confronti di qualcuno che smaltisce direttamente il riferimento monouso sottostante. Pensi che dovrebbe essere?

Ho anche notato che Microsoft stessa ha qualcosa chiamato RefCountDisposable , ma non è generico e non trovo documentazione utile.

    
risposta data 20.07.2014 - 20:24
fonte
2

La prima cosa che devi fare è separare esplicitamente i cicli di vita della connessione nei due scenari. Cercare di manipolare i due porterà solo alla confusione. A tal fine, rimuoverei completamente la variabile membro _connection .

Ecco cosa propongo:

Utilizza un "oggetto contesto" per gestire il tuo stato. Il tuo metodo Connect restituisce già un Connection , quindi per ora può fungere da oggetto di contesto. Rendi tutti i tuoi metodi API funzionali SyncTime , UpdateUsers , ecc. Prendi sempre questo oggetto contesto e agisci di conseguenza.

Quindi la tua implementazione diventa:

public class Client
{    
    public void SyncTime(Connection connection)
    {
        if (connection == ONE_TIME_CONNECTION)
        {
            using (Connection localConnection = client.Connect())
            {
                SyncTimeInternal(localConnection); 
            }
        }
        else
        {
            SyncTimeInternal(connection);
        }
    }

    private void SyncTimeInternal(Connection connection)
    {
        DoSomeWork();
        connection.DoSomeMoreWork();
        EvenMoreWork();
    }

    public static final Connection ONE_TIME_CONNECTION = null;
}

Quindi il tuo codice cliente diventa:

using (Connection connection = client.Connect())
{
  client.SyncTime(connection);
  client.UpdateUsers(connection);
  client.SomeOtherOperation(connection);
}

o solo:

client.SyncTime(Client.ONE_TIME_CONNECTION);

Si noti l'uso della costante per astrarre un valore null . In questo modo sarà più facile modificare gli interni in futuro.

Ovviamente, puoi estendere questo modello per creare un oggetto ClientContext esplicito contenente la connessione e altre informazioni sullo stato.

    
risposta data 19.07.2014 - 06:12
fonte

Leggi altre domande sui tag