Garantire che più implementazioni siano valide

3

Prevedo questo con alcune classi che mostrano ciò che sto cercando di fare

interface IDataField { /* ... */ }

class DataFieldImplementationA : IDataField { /* ... */ }

class DataFieldImplementationB : IDataField { /* ... */ }

interface IData
{
    IDataField Field { get; }
}

class DataImplementationA : IData
{
    public DataFieldImplementationA Field { get; set; }

    IDataField IData.Field => this.Field; // this gets messy quickly
}

class DataImplementationB : IData
{
    public DataFieldImplementationB Field { get; set; }

    IDataField IData.Field => this.Field; // this gets messy quickly
}

class Program
{
    static T Load<T>(/* ... */) where T : IData { /* ... */ }
    static void Save(IData data) { /* ... */ }

    static void Main(string[] args)
    {
        // load as a specific implementation, this is working fine
        var a = Load<DataImplementationA>( /* ... */ );
        var b = Load<DataImplementationB>( /* ... */ );
        // save as any implementation, the saving system just cares about the data
        Save(a);
        Save(b);
    }
}

Quello che ho sono due diverse rappresentazioni interne degli stessi dati. Uno è un dato piatto e uno supporta e traccia l'ereditarietà nei dati. Ho bisogno sia di fare diversi tipi di elaborazione.

Sono specificamente interessato a scoprire se esiste un approccio migliore per far rispettare le diverse proprietà senza dover avere una proprietà di interfaccia esplicita corrispondente sulle mie due implementazioni di dati, le righe in cui scrivo "questo diventa molto veloce".

Il mio obiettivo con l'interfaccia è se aggiorno le interfacce, questo mi costringerà ad aggiornare entrambe le mie implementazioni, in modo che entrambe funzionino e corrispondano.

In breve, ho bisogno di due classi per avere sempre le stesse proprietà, ma l'implementazione di queste proprietà potrebbe essere diversa dalle due classi. Fare questo con le interfacce sembra ovvio, ma siccome le proprietà possono essere implementazioni diverse, mi imbatto in una necessità di implementazioni esplicite dell'interfaccia che diventa complicata per le grandi classi di dati.

Domanda : come faccio a garantire che due classi abbiano le stesse proprietà con il minor numero di problemi?

Disclaimer: lo chiedo, sapendo bene che potrei andare su questo nel modo sbagliato. Se c'è un modo diverso di fare quello che sto facendo, anche queste risposte sono benvenute.

    
posta William Mariager 17.05.2017 - 14:46
fonte

1 risposta

1

Forse mi sono perso qualcosa (o sto facendo troppe ipotesi extra), ma perché no:

public interface IDataField { /* ... */ }

public class DataFieldImplementationA : IDataField { /* ... */ }

public class DataFieldImplementationB : IDataField { /* ... */ }

public interface IData
{
    void LoadFrom<TArgs>(TArgs args);

    IDataField Field { get; }
}

public abstract class DataImplementationBase<TDataField> : IData
    where TDataField : class, IDataField, new()
{
    protected TDataField _field;

    public abstract void LoadFrom<TArgs>(TArgs args);

    public IDataField Field => _field ?? (_field = new TDataField());
}

public class DataImplementationA : DataImplementationBase<DataFieldImplementationA>
{
    public override void LoadFrom<TArgs>(TArgs args)
    {
        /* load _field from args
           (will need cast, eg: ... (int)args ...) */
    }
}

public class DataImplementationB : DataImplementationBase<DataFieldImplementationB>
{
    public override void LoadFrom<TArgs>(TArgs args)
    {
        /* load _field from args
           (will need cast, eg: ... (string)args ...) */
    }
}

class Program
{
    static TData Load<TData, TArgs>(TData prototype, TArgs args)
        where TData : class, IData, new()
    {
        var data = new TData();
        data.LoadFrom(args);
        return data;
    }

    static void Save(IData data) { /* ... */ }

    static void Main(string[] args)
    {
        // load as a specific implementation, this is working fine
        var a = Load(default(DataImplementationA), 123);
        var b = Load(default(DataImplementationB), "foo");
        // save as any implementation, the saving system just cares about the data
        Save(a);
        Save(b);
    }
}

Motivazione:

Ho capito che vuoi che diverse implementazioni di IData siano vincolate dal tipo concreto di diverse implementazioni di IDataField;

quindi, l'uso del vincolo parametrico generico (TDataField) sulla classe generica base DataImplementationBase;

Inoltre, i discendenti di quest'ultimo, proprio per scopi di implementazione, dovranno avere accesso all'implementazione di IDataField strongmente tipizzata, quindi il membro TDataField protetto (_field) utilizzato per implementare il getter della proprietà Field di IData.

Infine, il caricamento di un'istanza di IData richiederà probabilmente un contesto del chiamante tipizzato che varierà con il tipo di implementazione concreto scelto per IData, quindi l'introduzione di un metodo LoadFrom (TArgs args) generico sull'interfaccia IData.

Il parametro "TData prototype" del metodo statico Load helper è solo per l'inferenza di tipo conveniente insieme agli argomenti nei siti di chiamata.

    
risposta data 19.05.2017 - 11:36
fonte

Leggi altre domande sui tag