Adattamento delle conversioni di terze parti

0

Ho un paio di funzionalità simili ma non esattamente le stesse. Entrambi implicano la conversione di un tipo che possiedo in altri tipi che appartengono a una libreria di terze parti, quindi fuori dal mio controllo.

Sono relativamente inesperto nell'usare correttamente i modelli di progettazione ed è qualcosa a cui sto cercando di migliorare. Mi sembra che questo problema sia qualcosa che potrebbe essere risolto con qualcosa come un adattatore, ma i due non si sposano abbastanza. Ecco una versione molto semplificata / semplificata di come appare il mio codice:

public class ThirdPartyTypeConverter : ITypeConverter
{
    ...

    public ThirdPartyTypeA[] Convert(MyType source)
    {
        switch (source.EnumCondition)
        {
            ...
            case ConditionWeCareAbout:
                return source.Collection.Select(c => new ThirdPartyTypeA
                {
                    Id = c.Id,
                    Name = c.BuildName,
                    Action = c.Action
                };
        }
    }
}

...

public class AnotherThirdPartyTypeConverter : ITypeConverter
{
    ...

    public ThirdPartyTypeB Convert(MyType source)
    {
        switch (source.EnumCondition)
        { 
            ...
            case ConditionWeCareAbout:
                return new ThirdPartyTypeB
                {
                    Id = c.Id,
                    Name = c.BuildName,
                    Action = c.Action
                };
        }
    }
}

Quelle 2 conversioni su questi casi switch fanno praticamente la stessa cosa, tranne che si associano a tipi diversi. Inizialmente mi sono chiesto se potevo avere uno strato intermedio per estrarlo ma non c'è modo di sapere come generare un tipo al volo (in realtà non corrispondono perfettamente).

È qualcosa che può essere scomposto o è qualcosa che deve essere vissuto con?

    
posta Serberuss 17.07.2018 - 20:35
fonte

1 risposta

2

Il nocciolo del problema qui è che non sei in grado di creare codice che funzioni sia per ThirdPartyTypeA che per ThirdPartyTypeB , perché non esiste un collegamento formale tra di loro , e per questo motivo il compilatore rifiuta di capire / riconoscere che il codice pubblicato funziona per entrambi i tipi.

Ci sono molti modi per aggirare questo:

1. Implementa l'ereditarietà

Meno appropriato per i tipi di terze parti, ma se tutti i tipi erano di tua proprietà, devi prima chiedertelo se esiste un'eredità comune tra i due, prima di passare al passaggio 2.

Voglio sottolineare qui che l'ereditarietà non è sempre migliore delle interfacce. L'ereditarietà può essere sfruttata e l'ereditarietà eccessiva è un dolore da affrontare.

Sto suggerendo di esaminare l'ereditarietà prima delle interfacce perché i problemi risolti dall'eredità spesso esistono su un ambito più ampio dei problemi risolti dalle interfacce. Guarda la macro (ereditarietà) prima di guardare il micro (interfacce).

2. Implementa interfacce

Quando non esiste una logica di ereditarietà; la seconda opzione è implementare un'interfaccia di base tra di loro.

Un semplice esempio:

public interface IFoo
{
    string Name;
}

public class ThirdPartyTypeA : IFoo
{
    public String Name { get; set; }
}

public class ThirdPartyTypeB : IFoo
{
    public String Name { get; set; }
}

E poi:

public T CreateFromMyType<T>(MyType myType) where T : IFoo, new()
{
    var obj = new T();

    obj.Name = myType.Name;

   return obj;
}

E il suo utilizzo:

ThirdPartyTypeA obj = CreateFromMyType<ThirdPartyTypeA>(myType);

Questo è solo un esempio rudimentale per mostrarti come implementarlo a livello base. Ci sono più incognite nel tuo codice di esempio (ad esempio, quanto sono diverse le condizioni enum, cosa cambia effettivamente tra i diversi valori enum, ...) che devono essere affrontati prima di poter creare una soluzione adatta.

3. Riflessione, se necessario.

Non mi piace l'uso eccessivo della riflessione. Tuttavia, ti dà la possibilità di ottenere / impostare le proprietà in base al nome di una stringa, ignorando così la sicurezza del tipo fornita da C #.

A meno che non si riesca a trovare un motivo giustificabile per non utilizzare le interfacce, considero la riflessione una soluzione inferiore per l'implementazione dell'interfaccia. Non perché non funzioni, ma perché getta la sicurezza del tipo fuori dalla porta, scambia gli errori del tempo di compilazione per gli errori di runtime, e è facile sparare al piede con (l'operatore nameof() aiuta non spararti ai piedi, ma è un miglioramento parziale).

4. Converti in / da un tipo serializzato da stringhe

Simile alla riflessione, è possibile convertire i dati in una stringa JSON (con il nome della proprietà comune) e quindi convertire tale JSON in un tipo di terze parti. I serializzatori basati su stringhe accedono alle proprietà in base al loro nome e ignorano la sicurezza del tipo di C #.

Questo è davvero solo usando il riflesso con un nome diverso. Evita leggermente il tipo di non sicurezza (perché puoi definire la tua classe DTO intermedia nel codice), ma getta ancora la sicurezza del tipo fuori dalla porta e scambia gli errori di compilazione per gli errori di runtime.

5. Passa a una lingua non sicura dal tipo.

Sono consapevole che questa non è una soluzione realistica, ma voglio segnalarlo.

La sicurezza del tipo esiste per un motivo. Prima che esistesse la sicurezza del tipo, le applicazioni dipendevano in gran parte dall'attitudine dello sviluppatore a gestire un complesso web di riferimenti di proprietà.

A similar thing happened for C/C++ pointers. While very powerful, they were also very complicated, easy to shoot yourself in the foot with, and nigh impossible to easily debug. This is why C# has moved on to use reference types, effectively having the compiler do the memory allocation for you.
Properly written C++ outperforms C#, but the difficulty in writing that perfect C++ is so much higher than working with reference types in C#.

Con la sicurezza del tipo, siamo allo stesso bivio. Facciamo affidamento sugli sviluppatori che gestiscono tutto da soli o automatizziamo per rendere le cose molto più semplici (a costo di piccoli inconvenienti)?

È possibile trovare interessanti discussioni su questo argomento seguendo le discussioni "Typescript vs Javascript". Typescript è stato creato appositamente per introdurre la sicurezza del tipo per il Javascript altrimenti di tipo non sicuro. Direi che l'attuale crescente popolarità di Typescript dimostra che la sicurezza di tipo è qualcosa che il settore dello sviluppo vuole avere.

A meno che non si crei un collegamento formale tra le due classi di terze parti (ereditarietà o interfaccia), si sta effettivamente tentando di ignorare la regola di sicurezza del tipo applicata globalmente. Questo suggerisce che il tuo approccio non è semplicemente compatibile con l'approccio di C #.

Suggerisco di adattare il tuo approccio invece di cercare di aggirare l'ideologia della lingua in cui stai programmando.

    
risposta data 18.07.2018 - 14:46
fonte