Best practice relative alla mappatura dei tipi e ai metodi di estensione


15

Voglio porre alcune domande sulle migliori pratiche relative ai tipi di mappatura e all'utilizzo dei metodi di estensione in C #. So che questo argomento è stato discusso più volte negli ultimi anni, ma ho letto molti post e ho ancora dubbi.

Il problema che ho riscontrato era l'estensione della classe che possiedo con la funzionalità "converti". Diciamo che ho una classe "Person" che rappresenta un oggetto che verrà usato da qualche logica. Ho anche una classe "Cliente" che rappresenta una risposta da API esterna (in realtà ci sarà più di un'API, quindi ho bisogno di mappare la risposta di ciascuna API al tipo comune: Person). Ho accesso al codice sorgente di entrambe le classi e teoricamente posso implementare i miei metodi lì. Devo convertire il cliente in persona per poterlo salvare nel database. Il progetto non utilizza alcun mappatore automatico.

Ho in mente 4 possibili soluzioni:

  1. Metodo .ToPerson () nella classe Consumer. È semplice, ma sembra spezzare il modello di responsabilità singola per me, soprattutto che la classe Consumer è mappata su altre classi (alcune richieste anche da un'altra API esterna), quindi dovrebbe contenere più metodi di mappatura.

  2. Costruttore di mappatura nella classe Person prendendo Consumer come argomento. Anche facile e sembra anche rompere il modello di responsabilità singola. Avrei bisogno di più costruttori di mapping (poiché ci sarà classe da un'altra API, fornendo gli stessi dati di Consumer ma in un formato leggermente diverso)

  3. Classe di convertitori con metodi di estensione. In questo modo posso scrivere il metodo .ToPerson () per la classe Consumer e quando viene introdotta un'altra API con la propria classe NewConsumer, posso semplicemente scrivere un altro metodo di estensione e conservare tutto nello stesso file. Ho sentito un'opinione secondo cui i metodi di estensione sono malvagi in generale e dovrebbero essere usati solo se assolutamente necessari, quindi questo è ciò che mi trattiene. Altrimenti mi piace questa soluzione

  4. Classe Converter / Mapper. Creo una classe separata che gestirà le conversioni e implementerò metodi che prenderanno l'istanza della classe di origine come argomento e restituiranno l'istanza della classe di destinazione.

Per riassumere, il mio problema può essere ridotto al numero di domande (tutte nel contesto di quello che ho descritto sopra):

  1. Mettere il metodo di conversione all'interno (POCO?) Dell'oggetto (come il metodo .ToPerson () nella classe Consumer) è considerato rompere il singolo modello di responsabilità?

  2. L'uso di costruttori di conversione in classe (simile a DTO) è considerato rompere il modello di responsabilità singola? Soprattutto se tale classe può essere convertita da più tipi di sorgente, quindi sarebbero necessari più costruttori di conversione?

  3. L'uso di metodi di estensione pur avendo accesso al codice sorgente della classe originale è considerato una cattiva pratica? Tale comportamento può essere usato come modello praticabile per separare la logica o è un anti-modello?


La Personclasse è una DTO? contiene qualche comportamento?
Yacoub Massad,

Per quanto ne so è tecnicamente un DTO. Contiene alcune logiche "iniettate" come metodi di estensione, ma questa logica è limitata al tipo di metodi "ConvertToThatClass". Questi sono tutti metodi legacy. Il mio compito è quello di implementare nuove funzionalità che utilizzano alcune di queste classi, ma mi è stato detto che non dovrei attenermi all'approccio attuale se è male solo per mantenerlo coerente. Quindi mi chiedo se questo approccio esistente con la conversione usando i metodi di estensione sia buono e se dovrei
attenermi

Risposte:


11

Mettere il metodo di conversione all'interno (POCO?) Dell'oggetto (come il metodo .ToPerson () nella classe Consumer) è considerato rompere il singolo modello di responsabilità?

Sì, perché la conversione è un'altra responsabilità.

L'uso di costruttori di conversione in classe (simile a DTO) è considerato rompere il modello di responsabilità singola? Soprattutto se tale classe può essere convertita da più tipi di sorgente, quindi sarebbero necessari più costruttori di conversione?

Sì, la conversione è un'altra responsabilità. Non fa differenza se lo fai tramite costruttori o tramite metodi di conversione (ad es ToPerson.).

L'uso di metodi di estensione pur avendo accesso al codice sorgente della classe originale è considerato una cattiva pratica?

Non necessariamente. È possibile creare metodi di estensione anche se si dispone del codice sorgente della classe che si desidera estendere. Penso che se si crea o meno un metodo di estensione dovrebbe essere determinato dalla natura di tale metodo. Ad esempio, contiene molta logica? Dipende da qualcos'altro che i membri dell'oggetto stesso? Direi che non dovresti avere un metodo di estensione che richiede dipendenze per funzionare o che contiene una logica complessa. Solo la logica più semplice dovrebbe essere contenuta in un metodo di estensione.

Tale comportamento può essere usato come modello praticabile per separare la logica o è un anti-modello?

Se la logica è complessa, penso che non dovresti usare un metodo di estensione. Come ho notato prima, dovresti usare i metodi di estensione solo per le cose più semplici. Non considererei la conversione semplice.

Ti suggerisco di creare servizi di conversione. Puoi avere un'unica interfaccia generica per questo in questo modo:

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

E puoi avere convertitori come questo:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

E puoi usare Dependency Injection per iniettare un convertitore (ad esempio IConverter<Person,Customer>) in qualsiasi classe che richiede la possibilità di convertire tra Persone Customer.


Se non sbaglio, c'è già un IConverterframework, che aspetta solo di essere implementato.
RubberDuck,

@RubberDuck, dove esiste?
Yacoub Massad,

Stavo pensando IConvertable, che non è quello che stiamo cercando qui. Errore mio.
RubberDuck,

Ah ah! Ho trovato quello che stavo pensando a @YacoubMassad. Converterviene utilizzato da Listquando si chiama ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Non so quanto sia utile OP.
RubberDuck,

Anche rilevante. Qualcun altro ha adottato l'approccio che proponi qui. codereview.stackexchange.com/q/51889/41243
RubberDuck

5

Mettere il metodo di conversione all'interno (POCO?) Dell'oggetto (come il .ToPerson()metodo nella classe Consumer) è considerato rompere il singolo modello di responsabilità?

Sì. Una Consumerclasse è responsabile per contenere i dati relativi a un consumatore (ed eventualmente eseguire alcune azioni) e non deve essere responsabile per la conversione stessa in un altro tipo non correlato.

L'uso di costruttori di conversione in classe (simile a DTO) è considerato rompere il modello di responsabilità singola? Soprattutto se tale classe può essere convertita da più tipi di sorgente, quindi sarebbero necessari più costruttori di conversione?

Probabilmente. In genere ho metodi al di fuori del dominio e oggetti DTO per fare la conversione. Uso spesso un repository che contiene oggetti di dominio e (de) li serializza in un database, memoria, file o altro. Se inserisco quella logica nelle mie classi di dominio, ora le ho legate a una particolare forma di serializzazione, che è dannosa per i test (e altre cose). Se inserisco la logica nelle mie classi DTO, le ho legate al mio dominio, limitando nuovamente i test.

L'uso di metodi di estensione pur avendo accesso al codice sorgente della classe originale è considerato una cattiva pratica?

Non in generale, sebbene possano essere abusati. Con i metodi di estensione, si creano estensioni opzionali che facilitano la lettura del codice. Hanno degli svantaggi: è più facile creare ambiguità che il compilatore deve risolvere (a volte in silenzio) e può rendere più difficile il debug del codice poiché non è del tutto ovvio da dove provenga il metodo di estensione.

Nel tuo caso, una classe di convertitore o mapper sembra l'approccio più semplice e diretto, anche se non penso che tu stia facendo qualcosa di sbagliato usando i metodi di estensione.


2

Che ne dite di usare AutoMapper o mapper personalizzato potrebbe essere usato Mi piace

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

dal dominio

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

Sotto il cofano potresti astrarre AutoMapper o rotolare il tuo mappatore


2

So che questo è vecchio, ma è ancora rivelatore. Ciò ti consentirà di convertire in entrambi i modi. Lo trovo utile quando lavoro con Entity Framework e la creazione di viewmodels (DTO).

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

Trovo AutoMapper un po 'troppo per fare un'operazione di mappatura davvero semplice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.