Prevenire alcuni aspetti negativi di una classe statica

1

Ecco la mia situazione. Ho implementato la risposta .NET 4.0 accettata in questa domanda , che funziona.

Tuttavia, il mio codebase è abbastanza grande e in quanto responsabile di questo codice ho iniziato a ricevere informazioni su questa soluzione. Mi è stato chiesto se non ci sono modi più semplici per realizzare la stessa cosa e se c'è un modo per accorciare l'utilizzo.

In quasi tutti i casi, questa classe viene utilizzata per ottenere o impostare una sola proprietà. Questo favorisce altre soluzioni, dal momento che l'utilizzo finisce così: new UserSessionData().CustomerNumber = 123;

La cosa più vicina che ho è una classe (statica o meno) con membri e metodi statici, come questa:

public static class UserSessionData
{
    public static Dictionary<string, string> AsDictionary()
    {
        return typeof(UserSessionData).GetProperties()
                   .ToDictionary(p => p.Name, p => p.GetValue(null, null) as string);
    }
    private static string Get(Expression<Func<string>> e)
    {
        var me = e.Body as MemberExpression;
        return ExternalInterfaceClass.Get(me.Member.Name);
    }
    private static void Set(Expression<Func<string>> e, string value)
    {
        var me = e.Body as MemberExpression;
        ExternalInterfaceClass.Set(me.Member.Name, value);
    }

    public static string CustomerNumber {
        get { return Get(() => CustomerNumber); }
        set { Set(() => CustomerNumber, value); }
    }

    // ... and hundreds more properties similar to "CustomerNumber"
}

Questa sembra una soluzione abbastanza buona:

  • È strongmente tipizzato (cioè posso facilmente refactificare CustomerNumber in tutto)
  • L'utilizzo è ottimo ( UserSessionData.CustomerNumber = 123; )
  • Lo standard non è quello brutto - penso sia accettabile

Tuttavia, c'è un problema che non riesco a capire. A volte dobbiamo essere in grado di usarlo un po 'diversamente, A) come tipo di ritorno e B) modificare in blocco la struttura prima di salvare. Per quanto riguarda A, comprendo che una classe statica non può essere utilizzata come tipo di ritorno, tuttavia non so quali sarebbero le mie alternative in questa specifica situazione?

Per quanto riguarda B, immagino di poter ritardare il risparmio effettivo fornendo una sottoclasse dichiarata e richiedendo un salvataggio esplicito, ma l'utilizzo sarebbe peggiorato per il normale caso d'uso (impostazione o ottenimento di una singola proprietà) poiché ogni one-liner dovrebbe essere seguito da una chiamata al metodo di salvataggio.

Ho pensato di creare un'interfaccia per la classe, ma le interfacce non possono definire proprietà statiche. Mi sento un po 'confuso qui, l'aiuto è apprezzato. Fornisci un esempio di codice minimale quando presenti i tuoi pensieri, se possibile - chiarisce molto il punto.

    
posta Simeon 29.01.2014 - 11:33
fonte

2 risposte

2

Non è esattamente una risposta alla tua domanda, ma hai esaminato i modelli T4?

Ho l'impressione che stiate codificando manualmente le proprietà di queste piastre. Se effettivamente seguono uno schema quasi identico e desideri una strong digitazione prova a scrivere il codice che genera codice ...

Un T4 ti permette di scorrere i tuoi oggetti e sostanzialmente di costruire un file di codice, che verrà valutato durante la compilazione, questo significherebbe quando aggiungi / rimuovi gli elementi dai tuoi dati di sessione che aggiungerà / eliminerà quegli accessor e mantieni aggiornato il tuo codice e ti consente di impostare lo standard di progettazione in un unico punto e di farlo propagare automaticamente in un ciclo su tutte le proprietà future.

Un esempio comune di perché / dove dovresti usare questo sarebbe un sistema ORM roll-your-own per mappare una tabella su una tabella di database senza una libreria enorme se non hai bisogno di tutte le operazioni CRUD ...

    
risposta data 29.01.2014 - 12:51
fonte
1

Prima di tutto: sembra strano che la classe sia astratta. Una classe astratta deve essere una classe base che non può essere istanziata e contiene alcuni metodi astratti che devono essere implementati dalle classi derivate. Se tutti i metodi e le proprietà della classe sono statici e non vuoi che la classe sia in grado di essere istanziata, puoi semplicemente rendere statica anche la classe:

public static class UserSessionData
{
    ... // all static methods and properties and fields

In secondo luogo: non farlo in questo modo. Sul serio. Non farlo.

Il tuo codice di esempio mostra che la tua classe UserSessionData accede anche a ExternalInterfaceClass che sembra essere anche una classe statica. Qui sta diventando davvero brutto - lo stato globale a bizzeffe. Il codice di test unitario come questo è estremamente doloroso (stato fatto lì).

Non vedo il motivo per cui la classe dovrebbe essere statica. Non c'è niente di sbagliato nell'avere un'istanza di ciò che viene passato in giro. Andando ancora oltre la classe dovrebbe ottenere l'interfaccia esterna iniettata come dipendenza in modo da poterla deridere. Per inciso, questo potrebbe darti la soluzione giusta per il tuo problema di modifica di massa.

Proviamo qualcosa:

public class UserSessionData : IUserSessionData
{
    private IExternalInterface _ExternalInterface;

    public UserSessionData(IExternalInterface externalInterface)
    {
        _ExternalInterface = externalInterface;
    }

    protected virtual string Get(Expression<Func<string>> e)
    {
        var me = e.Body as MemberExpression;
        return _ExternalInterface.Get(me.Member.Name);
    }

    protected virtual void Set(Expression<Func<string>> e, string value)
    {
        var me = e.Body as MemberExpression;
        _ExternalInterface.Set(me.Member.Name, value);
    }

    public Dictionary<string, string> AsDictionary()
    {
        return typeof(UserSessionData).GetProperties()
                   .ToDictionary(p => p.Name, p => p.GetValue(this) as string);
    }

    public IBulkEditableUserSessionData AsBulkEditable()
    {
        return new BulkEditableUserSessionData(_ExternalInterface);
    }

    public string CustomerNumber {
        get { return Get(() => CustomerNumber); }
        set { Set(() => CustomerNumber, value); }
    }

    // ... hundreds of properties

    private class BulkEditableUserSessionData : UserSessionData, IBulkEditableUserSessionData 
    {
        private Dictionary<string, string> _EditCache = new Dictionary<string, string>();

        public BulkEditableUserSessionData(IExternalInterface externalInterface)
            : base(externalInterface)
        {
        }

        protected override string Get(Expression<Func<string>> e)
        {
            var fieldName = ((MemberExpression)e.Body).Member.Name;
            // return modified value from cache if present
            string value;
            if (_EditCache.TryGet(fieldName, out value))
            {
                return value;
            }
            return _ExternalInterface.Get(fieldName);
        }

        protected virtual void Set(Expression<Func<string>> e, string value)
        {
            var fieldName = ((MemberExpression)e.Body).Member.Name;
            _EditCache[fieldName] = value;
        }

        public void Save()
        {
            foreach (var kvp in _EditCache)
            {
                _ExternalInterface.Set(kvp.Key, kvp.Value);
            }
        }
    }
}


public interface IExternalInterface
{
    string Get(string fieldName);
    void Set(string fieldName, string value);
}

public interface IUserSessionData
{
    Dictionary<string, string> AsDictionary();

    string CustomerNumber { get; set; }

    // .. all the other hundreds of properties
}

public interface IBulkEditableUserSessionData : IUserSessionData
{
    void Save();
}

Utilizzo previsto:

In un punto del ciclo di vita dell'applicazione (presumibilmente da qualche parte nella fase di inizializzazione) viene creata un'istanza UserDataSession e viene iniettata l'interfaccia esterna.

Qualsiasi codice che abbia bisogno di accedere ai dati della sessione utente riceve quell'istanza (ad esempio, passata come parametro costruttore) e può usarla in questo modo:

public class BusinessClass
{
    private IUserSessionData _UserSessionData;

    public BusinessClass(..., IUserSessionData userSessionData, ...)
    {
        _UserSessionData = userSessionData;
    }

    public void UpdateCustomerNumber(string newCustomerNumber)
    {
        _UserSessionData.CustomerNumber = newCustomerNumber;
    }

    public void UpdateManyProperties()
    {
        var bulkEdit = _UserSessionData.AsBulkEditable();

        bulkEdit.CustomerNumber = ...
        bulkEdit.CustomerPhone = ...
        bulkEdit.CustomerWhatever = ...

        bulkEdit.Save();
    }
}

Il design può essere rifinito e probabilmente necessita di un aggiustamento in base ai tuoi casi d'uso, ma dovrebbe illustrare l'idea.

Un leggero svantaggio è che devi aggiungere nuove proprietà alla classe e all'interfaccia, ma questo non dovrebbe essere un problema. Se ti dimentichi di aggiungerlo all'interfaccia, i tuoi utenti si lamenteranno che non è disponibile. E se ti dimentichi di aggiungerlo all'implementazione, il compilatore si lamenterà.

    
risposta data 30.01.2014 - 11:24
fonte

Leggi altre domande sui tag