Domanda

Ho un progetto in cui ho bisogno di costruire una discreta quantità di dati di configurazione prima di poter eseguire un processo. Durante la fase di configurazione, è molto conveniente avere i dati come mutevole. Tuttavia, una volta che la configurazione è stata completata, mi piacerebbe passare una visione immutabile di che i dati per il processo funzionale, come quel processo si baserà sulla immutabilità di configurazione per molti dei suoi calcoli (le cose per esempio, la possibilità di pre-elaborazione basata sulla configurazione iniziale.) mi è venuta in mente una possibile soluzione utilizzando interfacce per esporre una vista in sola lettura, ma mi piacerebbe sapere se qualcuno ha riscontrato problemi con questo tipo di approccio o se ci sono altre raccomandazioni su come risolvere questo problema.

Un esempio del pattern Attualmente sto usando:

public interface IConfiguration
{
    string Version { get; }

    string VersionTag { get; }

    IEnumerable<IDeviceDescriptor> Devices { get; }

    IEnumerable<ICommandDescriptor> Commands { get; }
}

[DataContract]
public sealed class Configuration : IConfiguration
{
    [DataMember]
    public string Version { get; set; }

    [DataMember]
    public string VersionTag { get; set; }

    [DataMember]
    public List<DeviceDescriptor> Devices { get; private set; }

    [DataMember]
    public List<CommandDescriptor> Commands { get; private set; }

    IEnumerable<IDeviceDescriptor> IConfiguration.Devices
    {
        get { return Devices.Cast<IDeviceDescriptor>(); }
    }

    IEnumerable<ICommandDescriptor> IConfiguration.Commands
    {
        get { return Commands.Cast<ICommandDescriptor>(); }
    }

    public Configuration()
    {
        Devices = new List<DeviceDescriptor>();
        Commands = new List<CommandDescriptor>();
    }
}

Modifica

Sulla base di input da Mr. Lippert e cdhowie, ho messo insieme la seguente (rimosso alcune proprietà per semplificare):

[DataContract]
public sealed class Configuration
{
    private const string InstanceFrozen = "Instance is frozen";

    private Data _data = new Data();
    private bool _frozen;

    [DataMember]
    public string Version
    {
        get { return _data.Version; }
        set
        {
            if (_frozen) throw new InvalidOperationException(InstanceFrozen);
            _data.Version = value;
        }
    }

    [DataMember]
    public IList<DeviceDescriptor> Devices
    {
        get { return _data.Devices; }
        private set { _data.Devices.AddRange(value); }
    }

    public IConfiguration Freeze()
    {
        if (!_frozen)
        {
            _frozen = true;
            _data.Devices.Freeze();
            foreach (var device in _data.Devices)
                device.Freeze();
        }
        return _data;
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context)
    {
        _data = new Data();
    }

    private sealed class Data : IConfiguration
    {
        private readonly FreezableList<DeviceDescriptor> _devices = new FreezableList<DeviceDescriptor>();

        public string Version { get; set; }

        public FreezableList<DeviceDescriptor> Devices
        {
            get { return _devices; }
        }

        IEnumerable<IDeviceDescriptor> IConfiguration.Devices
        {
            get { return _devices.Select(d => d.Freeze()); }
        }
    }
}

FreezableList<T> è, come ci si aspetterebbe, un'implementazione freezable di IList<T>. Questo benefici guadagni di isolamento, a costo di una certa complessità.

È stato utile?

Soluzione

L'approccio si descrive grandi opere se il "cliente" (il consumatore dell'interfaccia) e il "server" (il fornitore della classe) ha un accordo reciproco che:

  • il cliente sarà educato e non cercare di approfittare dei dettagli di implementazione del server
  • il server sarà educato e non mutare l'oggetto dopo che il cliente ha un riferimento ad esso.

Se non si dispone di un buon rapporto di lavoro tra le persone che scrivono il cliente e le persone che scrivono il server poi le cose vanno in modo rapido a forma di pera. Un client può maleducato, naturalmente, "gettato via" l'immutabilità lanciando al tipo di configurazione pubblico. Un server maleducato può distribuire una visione immutabile e quindi mutare l'oggetto quando il client meno se lo aspetta.

Un approccio bello è quello di prevenire il cliente da aver mai visto il tipo mutabile:

public interface IReadOnly { ... }
public abstract class Frobber : IReadOnly
{
    private Frobber() {}
    public class sealed FrobBuilder
    {
        private bool valid = true;
        private RealFrobber real = new RealFrobber();
        public void Mutate(...) { if (!valid) throw ... }
        public IReadOnly Complete { valid = false; return real; }
    }
    private sealed class RealFrobber : Frobber { ... }
}

Ora, se si desidera creare e mutare un Frobber, si può fare un Frobber.FrobBuilder. Quando hai finito il tuo mutazioni, si chiama completa e ottenere un'interfaccia di sola lettura. (E poi il costruttore non è più valido.) Dal momento che tutti i dettagli di implementazione mutevolezza sono nascosti in una classe privata annidata, non è possibile "cast away" l'interfaccia IReadOnly a RealFrobber, solo per Frobber, che non ha metodi pubblici!

Né può il cliente ostile creare il proprio Frobber, perché Frobber è astratta e ha un costruttore privato. L'unico modo per fare un Frobber è tramite il costruttore.

Altri suggerimenti

Questo funzionerà, ma i metodi "maligni" può tentare di lanciare un IConfiguration ad un Configuration e quindi bypassare il vostro restrizioni interfaccia-imposto. Se non siete preoccupati che allora il vostro approccio funzionerà bene.

faccio di solito qualcosa di simile:

public class Foo {
    private bool frozen = false;

    private string something;

    public string Something {
        get { return something; }
        set {
            if (frozen)
                throw new InvalidOperationException("Object is frozen.");

            // validate value

            something = value;
        }
    }

    public void Freeze() {
        frozen = true;
    }
}

In alternativa, si potrebbe profonda-clone vostre classi mutabili in classi immutabili.

Perché non si può fornire una visione immutabili separata dell'oggetto?

public class ImmutableConfiguration {
    private Configuration _config;
    public ImmutableConfiguration(Configuration config) { _config = config; }
    public string Version { get { return _config.Version; } }
}

o se non ti piace la tipizzazione in più, rendere i membri del set interno piuttosto che pubblica -? Accessibili all'interno del gruppo, ma non per i clienti di esso

Sto regolarmente a lavorare con un grande, framework basato-COM (ESRI ArcGIS Engine) che gestisce le modifiche in modo molto simile in alcune situazioni: ci sono i "default" interfacce IFoo per sola lettura accesso, e le interfacce IFooEdit (se del caso ) per le modifiche.

Tale quadro è abbastanza noto, e io non sono a conoscenza di eventuali reclami diffuse su questa particolare decisione di progettazione dietro di esso.

Infine, penso che sicuramente vale la pena qualche pensiero ulteriore nel decidere quale "prospettiva" viene ad essere quello di default: la prospettiva di sola lettura o accesso completo uno. Io personalmente fare la sola lettura vedere il default.

Come su:

struct Readonly<T>
{
    private T _value;
    private bool _hasValue;

    public T Value
    {
        get
        {
            if (!_hasValue)
                throw new InvalidOperationException();
            return _value;
        }
        set
        {
            if (_hasValue)
                throw new InvalidOperationException();
            _value = value;
        }
    }
}


[DataContract]
public sealed class Configuration
{
    private Readonly<string> _version;

    [DataMember]
    public string Version
    {
        get { return _version.Value; }
        set { _version.Value = value; }
    }
}

Ho chiamato Readonly ma non sono sicuro che è il nome migliore per questo però.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top