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

10

Voglio porre alcune domande sulle migliori pratiche riguardanti i tipi di mapping e l'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 dei dubbi.

Il problema che ho riscontrato è stato l'estensione della classe che possiedo con la funzionalità "convert". Diciamo che ho una "Persona" di classe che rappresenta un oggetto che sarà usato da qualche logica. Ho anche una classe "Cliente" che rappresenta una risposta da API esterna (in realtà ci saranno più di una API, quindi ho bisogno di mappare la risposta di ogni API al tipo comune: Person). Ho accesso al codice sorgente di entrambe le classi e in teoria posso implementare i miei metodi lì. Devo convertire il cliente in persona in modo che possa salvarlo nel database. Il progetto non utilizza alcun programma di mappatura automatico.

Ho in mente 4 possibili soluzioni:

  1. .ToPerson () metodo nella classe Consumer. È semplice, ma mi sembra di rompere il pattern di Single Responsibility, specialmente se la classe Consumer è mappata ad altre classi (alcune richieste da un'altra API esterna), quindi dovrebbe contenere più metodi di mapping.

  2. Costruttore di mapping nella classe Person che accetta Consumer come argomento. Anche facile e sembra anche spezzare il modello di Responsabilità Unica. Avrei bisogno di avere più costruttori di mappatura (dato che 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 .PoPerson () per la classe Consumer e quando viene introdotta un'altra API con la sua classe NewConsumer, posso semplicemente scrivere un altro metodo di estensione e mantenerlo tutto nello stesso file. Ho sentito un'opinione secondo cui i metodi di estensione sono in generale cattivi e dovrebbero essere usati solo se assolutamente necessari, quindi è questo che mi trattiene. Altrimenti mi piace questa soluzione

  4. Classe Convertitore / Mappatore. Creo una classe separata che gestirà le conversioni e implementerà metodi che considereranno 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 (il tutto nel contesto di ciò che ho descritto sopra):

  1. L'inserimento di un metodo di conversione all'interno dell'oggetto (POCO?) (come il metodo .ToPerson () nella classe Consumer) considera la rottura del modello di responsabilità singola?

  2. Sta usando i costruttori di convertitori in una classe (DTO-like) considerata la rottura del modello di responsabilità singola? Soprattutto se tale classe può essere convertita da più tipi di sorgenti, quindi sarebbero necessari più costruttori di conversione?

  3. Sta usando i metodi di estensione pur avendo accesso al codice sorgente di classe originale considerato una cattiva pratica? Questo comportamento può essere usato come modello valido per separare la logica o è un anti-pattern?

posta emsi 03.11.2015 - 13:23
fonte

4 risposte

9

Is putting conversion method inside (POCO?) object (like .ToPerson() method in Consumer class) considered breaking single responsibility pattern?

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

Is using converting constructors in (DTO-like) class considered breaking single responsibility pattern? Especially if such class can be converted from multiple source types, so multiple converting constructors would be required?

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

Is using extension methods while having access to original class source code considered bad practice?

Non necessariamente. È possibile creare metodi di estensione anche se si ha il codice sorgente della classe che si desidera estendere. Penso che la creazione di un metodo di estensione o meno debba essere determinata dalla natura di tale metodo. Ad esempio, contiene un sacco di logica? Dipende da qualsiasi altra cosa che i membri dell'oggetto stesso? Direi che non dovresti avere un metodo di estensione che richiede che le dipendenze funzionino o che contengano una logica complessa. Solo la logica più semplice dovrebbe essere contenuta in un metodo di estensione.

Can such behavior be used as viable pattern for separating logic or is it an anti-pattern?

Se la logica è complessa, allora penso che non dovresti usare un metodo di estensione. Come ho notato in precedenza, dovresti usare i metodi di estensione solo per le cose più semplici. Non prenderei in considerazione 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 utilizzare Iniezione di dipendenza per iniettare un convertitore (ad esempio IConverter<Person,Customer> ) in qualsiasi classe che richiede l'abilità per convertire tra Person e Customer .

    
risposta data 03.11.2015 - 14:32
fonte
4

Is putting conversion method inside (POCO?) object (like .ToPerson() method in Consumer class) considered breaking single responsibility pattern?

Sì. Una classe Consumer è responsabile della conservazione dei dati relativi a un consumatore (e probabilmente dell'esecuzione di alcune azioni) e non dovrebbe essere responsabile della conversione di stesso in un altro tipo non correlato.

Is using converting constructors in (DTO-like) class considered breaking single responsibility pattern? Especially if such class can be converted from multiple source types, so multiple converting constructors would be required?

Probabilmente. Solitamente ho metodi al di fuori del dominio e degli oggetti DTO per eseguire la conversione. Uso spesso un repository che accetta oggetti di dominio e li de-serializza, sia su un database, su una memoria, su un file, qualunque sia. Se inserisco questa logica nelle mie classi di dominio, ora le ho legate a una particolare forma di serializzazione, il che è dannoso per i test (e altre cose). Se inserisco la logica nelle mie classi DTO, li ho collegati al mio dominio, limitando di nuovo i test.

Is using extension methods while having access to original class source code considered bad practice?

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

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

    
risposta data 03.11.2015 - 14:39
fonte
2

Come usare l'AutoMapper o il mapper personalizzato potrebbe essere usato come

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 si può astrarre AutoMapper o rollare il proprio mappatore

    
risposta data 03.11.2015 - 13:59
fonte
1

So che è vecchio, ma è ancora in voga. Questo ti permetterà di convertire in entrambi i modi. Trovo utile questo quando si lavora con Entity Framework e creando 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 che AutoMapper sia un po 'troppo per fare operazioni di mappatura davvero semplici.

    
risposta data 14.11.2018 - 22:09
fonte

Leggi altre domande sui tag