Praktik terbaik tentang pemetaan tipe dan metode ekstensi

15

Saya ingin mengajukan beberapa pertanyaan tentang praktik terbaik mengenai tipe pemetaan dan menggunakan metode ekstensi dalam C #. Saya tahu topik ini telah dibahas beberapa kali selama beberapa tahun terakhir, tetapi saya telah membaca banyak posting dan masih ragu.

Masalah yang saya temui adalah memperluas kelas yang saya miliki dengan fungsionalitas "convert". Katakanlah saya memiliki kelas "Person" yang mewakili objek yang akan digunakan oleh beberapa logika. Saya juga memiliki kelas "Pelanggan" yang mewakili respons dari API eksternal (sebenarnya akan ada lebih dari satu API, jadi saya perlu memetakan setiap respons API ke tipe umum: Orang). Saya memiliki akses ke kode sumber kedua kelas dan secara teoritis dapat menerapkan metode saya sendiri di sana. Saya perlu mengonversi Pelanggan ke Orang agar saya dapat menyimpannya ke basis data. Proyek tidak menggunakan pemetaan otomatis.

Saya memiliki 4 kemungkinan solusi dalam pikiran:

  1. Metode ToTerson () di kelas konsumen. Ini sederhana, tetapi sepertinya mematahkan pola Tanggung Jawab Tunggal kepada saya, terutama bahwa kelas Konsumen dipetakan ke kelas lain (beberapa diperlukan oleh API eksternal lain) juga, jadi itu perlu mengandung beberapa metode pemetaan.

  2. Memetakan konstruktor di kelas Person menggunakan Konsumen sebagai argumen. Juga mudah dan sepertinya juga melanggar pola Tanggung Jawab Tunggal. Saya perlu memiliki beberapa konstruktor pemetaan (karena akan ada kelas dari API lain, memberikan data yang sama dengan Konsumen tetapi dalam format yang sedikit berbeda)

  3. Kelas konverter dengan metode ekstensi. Dengan cara ini saya dapat menulis metode .ToPerson () untuk kelas Konsumen dan ketika API lain diperkenalkan dengan kelas NewConsumer itu sendiri, saya hanya dapat menulis metode ekstensi lain dan menyimpan semuanya dalam file yang sama. Saya pernah mendengar pendapat bahwa metode penyuluhan adalah kejahatan pada umumnya dan harus digunakan hanya jika benar-benar diperlukan sehingga itulah yang menghambat saya. Kalau tidak, saya suka solusi ini

  4. Kelas konverter / mapper. Saya membuat kelas terpisah yang akan menangani konversi dan menerapkan metode yang akan mengambil instance kelas sumber sebagai argumen dan mengembalikan instance kelas tujuan.

Singkatnya, masalah saya dapat dikurangi menjadi sejumlah pertanyaan (semua dalam konteks dengan apa yang saya jelaskan di atas):

  1. Apakah memasukkan metode konversi ke dalam objek (POCO?) (Seperti metode .ToPerson () di kelas Konsumen) dianggap melanggar pola tanggung jawab tunggal?

  2. Apakah menggunakan konstruktor konversi di (seperti DTO) dianggap melanggar pola tanggung jawab tunggal? Terutama jika kelas seperti itu dapat dikonversi dari beberapa jenis sumber, jadi beberapa konstruktor konversi akan diperlukan?

  3. Apakah menggunakan metode ekstensi sambil memiliki akses ke kode sumber kelas asli dianggap praktik buruk? Bisakah perilaku seperti itu digunakan sebagai pola yang layak untuk memisahkan logika atau apakah itu anti-pola?

emsi
sumber
Apakah Personkelas itu DTO? apakah itu mengandung perilaku?
Yacoub Massad
Sejauh yang saya tahu itu secara teknis DTO. Ini berisi beberapa logika "disuntikkan" sebagai metode ekstensi, tetapi logika ini terbatas pada jenis metode "ConvertToThatClass". Ini semua adalah metode warisan. Pekerjaan saya adalah mengimplementasikan fungsionalitas baru yang menggunakan beberapa kelas ini, tetapi saya diberitahu bahwa saya tidak boleh berpegang teguh pada pendekatan saat ini jika itu buruk hanya untuk tetap konsisten. Jadi saya bertanya-tanya apakah pendekatan yang ada ini dengan mengonversi menggunakan metode ekstensi adalah yang baik dan jika saya harus tetap menggunakannya, atau mencoba sesuatu yang lain
emsi

Jawaban:

11

Apakah memasukkan metode konversi ke dalam objek (POCO?) (Seperti metode .ToPerson () di kelas Konsumen) dianggap melanggar pola tanggung jawab tunggal?

Ya, karena pertobatan adalah tanggung jawab lain.

Apakah menggunakan konstruktor konversi di (seperti DTO) dianggap melanggar pola tanggung jawab tunggal? Terutama jika kelas seperti itu dapat dikonversi dari beberapa jenis sumber, jadi beberapa konstruktor konversi akan diperlukan?

Ya, pertobatan adalah tanggung jawab lain. Tidak ada bedanya jika Anda melakukannya melalui konstruktor atau melalui metode konversi (misalnya ToPerson).

Apakah menggunakan metode ekstensi sambil memiliki akses ke kode sumber kelas asli dianggap praktik buruk?

Belum tentu. Anda dapat membuat metode ekstensi bahkan jika Anda memiliki kode sumber kelas yang ingin Anda perluas. Saya pikir apakah Anda membuat metode ekstensi atau tidak harus ditentukan oleh sifat metode tersebut. Misalnya, apakah ini mengandung banyak logika? Apakah itu tergantung pada sesuatu yang lain dari anggota objek itu sendiri? Saya akan mengatakan bahwa Anda seharusnya tidak memiliki metode ekstensi yang memerlukan dependensi untuk bekerja atau yang mengandung logika kompleks. Hanya logika yang paling sederhana yang harus dimuat dalam metode ekstensi.

Bisakah perilaku seperti itu digunakan sebagai pola yang layak untuk memisahkan logika atau apakah itu anti-pola?

Jika logikanya kompleks, maka saya pikir Anda tidak harus menggunakan metode ekstensi. Seperti yang saya sebutkan sebelumnya, Anda harus menggunakan metode ekstensi hanya untuk hal-hal sederhana. Saya tidak akan menganggap konversi sederhana.

Saya sarankan Anda membuat layanan konversi. Anda dapat memiliki antarmuka generik tunggal untuk itu seperti ini:

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

Dan Anda dapat memiliki konverter seperti ini:

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
    }
}

Dan Anda bisa menggunakan Dependency Injection untuk menyuntikkan konverter (misalnya IConverter<Person,Customer>) ke kelas apa saja yang membutuhkan kemampuan untuk mengkonversi antara Persondan Customer.

Yacoub Massad
sumber
Jika saya tidak salah, sudah ada IConverterdalam framework, tinggal menunggu untuk diimplementasikan.
RubberDuck
@RubberDuck, di mana itu ada?
Yacoub Massad
Saya sedang memikirkan IConvertable, bukan itu yang kami cari di sini. Kesalahanku.
RubberDuck
Ah ha! Saya menemukan apa yang saya pikirkan tentang @YacoubMassad. Converterdigunakan Listsaat menelepon ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Saya tidak tahu seberapa berguna itu untuk OP.
RubberDuck
Juga relevan. Orang lain telah mengambil pendekatan yang Anda usulkan di sini. codereview.stackexchange.com/q/51889/41243
RubberDuck
5

Apakah memasukkan metode konversi ke dalam objek (POCO?) (Seperti .ToPerson()metode di kelas Konsumen) dianggap melanggar pola tanggung jawab tunggal?

Iya. Sebuah Consumerkelas bertanggung jawab untuk memegang data yang berhubungan dengan konsumen (dan mungkin melakukan beberapa tindakan) dan tidak bertanggung jawab untuk mengkonversi sendiri ke dalam, jenis yang tidak terkait lain.

Apakah menggunakan konstruktor konversi di (seperti DTO) dianggap melanggar pola tanggung jawab tunggal? Terutama jika kelas seperti itu dapat dikonversi dari beberapa jenis sumber, jadi beberapa konstruktor konversi akan diperlukan?

Mungkin. Saya biasanya memiliki metode di luar domain dan objek DTO untuk melakukan konversi. Saya sering menggunakan repositori yang mengambil objek domain dan (de) membuat serial mereka, baik ke database, memori, file, apa pun. Jika saya menempatkan logika itu di kelas domain saya, sekarang saya sudah mengikatnya ke satu bentuk serialisasi tertentu, yang buruk untuk pengujian (dan hal-hal lain). Jika saya menempatkan logika di kelas DTO saya, maka saya telah mengikatnya ke domain saya, lagi-lagi membatasi pengujian.

Apakah menggunakan metode ekstensi sambil memiliki akses ke kode sumber kelas asli dianggap praktik buruk?

Tidak secara umum, meskipun mereka bisa digunakan secara berlebihan. Dengan metode ekstensi, Anda membuat ekstensi opsional yang membuat kode lebih mudah dibaca. Mereka memang memiliki kekurangan - lebih mudah untuk menciptakan ambiguitas yang harus diselesaikan oleh kompiler (terkadang secara diam-diam) dan dapat membuat kode lebih sulit untuk di-debug karena tidak sepenuhnya jelas dari mana metode ekstensi berasal.

Dalam kasus Anda, kelas konverter atau mapper sepertinya pendekatan yang paling sederhana, paling mudah, meskipun saya tidak berpikir Anda melakukan kesalahan dengan menggunakan metode ekstensi.

D Stanley
sumber
2

Bagaimana dengan menggunakan AutoMapper atau custom mapper bisa digunakan Suka

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

dari domain

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

Di bawah tenda Anda bisa mengabstraksi AutoMapper atau menggulung mapper Anda sendiri

Anders
sumber
2

Saya tahu ini sudah tua, tetapi masih bersuka ria. Ini akan memungkinkan Anda untuk mengkonversi kedua cara. Saya menemukan ini membantu ketika bekerja dengan Entity Framework dan membuat viewmodels (DTO's).

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
    }
}

Saya menemukan AutoMapper sedikit banyak untuk melakukan operasi pemetaan yang sangat sederhana.

KC.
sumber