แนวปฏิบัติที่เหมาะสมที่สุดเกี่ยวกับการแมปประเภทและวิธีการขยาย


15

ฉันต้องการถามคำถามเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับประเภทการแมปและใช้วิธีการขยายใน C # ฉันรู้ว่าหัวข้อนี้มีการพูดคุยกันหลายครั้งในช่วงไม่กี่ปีที่ผ่านมา แต่ฉันได้อ่านบทความจำนวนมากและยังมีข้อสงสัย

ปัญหาที่ฉันพบคือการขยายคลาสที่ฉันเป็นเจ้าของด้วยฟังก์ชัน "แปลง" สมมติว่าฉันมีคลาส "บุคคล" ที่แสดงถึงวัตถุซึ่งจะถูกใช้โดยตรรกะบางอย่าง ฉันยังมีคลาส "ลูกค้า" ที่แสดงการตอบสนองจาก API ภายนอก (จริง ๆ แล้วจะมีมากกว่าหนึ่ง API ดังนั้นฉันต้องแมปการตอบสนองของ API แต่ละประเภททั่วไป: คน) ฉันสามารถเข้าถึงซอร์สโค้ดของทั้งชั้นเรียนและในทางทฤษฎีสามารถใช้วิธีการของตัวเองที่นั่น ฉันต้องการแปลงลูกค้าเป็นบุคคลเพื่อให้ฉันสามารถบันทึกลงในฐานข้อมูล โครงการไม่ได้ใช้ผู้ทำแผนที่อัตโนมัติใด ๆ

ฉันมีวิธีแก้ปัญหาที่เป็นไปได้ 4 ประการ:

  1. .ToPerson () วิธีการในระดับผู้บริโภค มันเรียบง่าย แต่ดูเหมือนว่าจะแบ่งรูปแบบความรับผิดชอบเดียวให้ฉันโดยเฉพาะอย่างยิ่งระดับ Consumer ถูกแมปกับคลาสอื่น ๆ (บางอันต้องการโดย API ภายนอกอื่น) ด้วยดังนั้นจึงต้องมีวิธีการทำแผนที่หลายวิธี

  2. การสร้างคอนสตรัคในคลาสบุคคลรับ Consumer เป็นอาร์กิวเมนต์ ง่ายและดูเหมือนว่าจะทำลายรูปแบบความรับผิดชอบเดียว ฉันต้องมีตัวสร้างการแมปหลายตัว (เนื่องจากจะมีคลาสจาก API อื่นให้ข้อมูลเดียวกับผู้บริโภค แต่ในรูปแบบที่แตกต่างกันเล็กน้อย)

  3. แปลงระดับด้วยวิธีการขยาย วิธีนี้ฉันสามารถเขียน. ToPerson () วิธีการสำหรับระดับผู้บริโภคและเมื่อมีการแนะนำ API อื่นที่มีคลาส NewConsumer ของตัวเองฉันสามารถเขียนวิธีการขยายอื่นและเก็บไว้ในไฟล์เดียวกัน ฉันเคยได้ยินความเห็นว่าวิธีการต่อเติมเป็นสิ่งที่ชั่วร้ายโดยทั่วไปและควรใช้เฉพาะเมื่อจำเป็นอย่างยิ่งเท่านั้นนั่นคือสิ่งที่ทำให้ฉันกลั้น มิฉะนั้นฉันชอบวิธีนี้

  4. คลาส Converter / Mapper ฉันสร้างคลาสแยกต่างหากที่จะจัดการการแปลงและใช้วิธีการที่จะใช้อินสแตนซ์ของคลาสซอร์สเป็นอาร์กิวเมนต์และส่งคืนอินสแตนซ์ของคลาสปลายทาง

เพื่อสรุปปัญหาของฉันสามารถลดจำนวนคำถามได้ (ตามบริบทที่ฉันอธิบายไว้ด้านบน):

  1. การวางวิธีการแปลงภายในวัตถุ (POCO?) (เช่น. ToPerson () วิธีการในระดับผู้บริโภค) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดียวหรือไม่

  2. การใช้คอนสตรัคเตอร์คอนสตรัคเตอร์ในคลาส (เหมือน DTO) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดี่ยวหรือไม่? โดยเฉพาะอย่างยิ่งหากคลาสดังกล่าวสามารถถูกแปลงจากแหล่งที่มาหลายประเภทดังนั้นคอนสตรัคเตอร์การแปลงหลายรายการจึงต้องการ

  3. การใช้วิธีการขยายในขณะที่มีการเข้าถึงรหัสต้นฉบับของคลาสถือว่าเป็นการปฏิบัติที่ไม่ดีหรือไม่? พฤติกรรมดังกล่าวสามารถใช้เป็นรูปแบบที่ทำงานได้สำหรับการแยกตรรกะหรือมันเป็นรูปแบบการต่อต้าน?


เป็นPersonชั้นเรียน DTO? มันมีพฤติกรรมใด ๆ
Yacoub Massad

เท่าที่ฉันรู้ว่ามันเป็นเทคนิค DTO มันมีตรรกะบางอย่าง "ฉีด" เป็นวิธีการขยาย แต่ตรรกะนี้ถูก จำกัด ให้วิธีการประเภท "ConvertToThatClass" นี่เป็นวิธีการดั้งเดิมทั้งหมด งานของฉันคือการใช้ฟังก์ชั่นใหม่ที่ใช้คลาสเหล่านี้บางอย่าง แต่ฉันบอกว่าฉันไม่ควรยึดติดกับวิธีการในปัจจุบันถ้ามันไม่ดีเพียงเพื่อให้มันสอดคล้องกัน ดังนั้นฉันสงสัยว่าวิธีการที่มีอยู่นี้กับการแปลงโดยใช้วิธีการขยายเป็นหนึ่งที่ดีและถ้าผมควรติดกับมันหรือลองอย่างอื่น
EMSI

คำตอบ:


11

การวางวิธีการแปลงภายในวัตถุ (POCO?) (เช่น. ToPerson () วิธีการในระดับผู้บริโภค) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดียวหรือไม่

ใช่เพราะการแปลงนั้นเป็นความรับผิดชอบอีกอย่างหนึ่ง

การใช้คอนสตรัคเตอร์คอนสตรัคเตอร์ในคลาส (เหมือน DTO) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดี่ยวหรือไม่? โดยเฉพาะอย่างยิ่งหากคลาสดังกล่าวสามารถถูกแปลงจากแหล่งที่มาหลายประเภทดังนั้นคอนสตรัคเตอร์การแปลงหลายรายการจึงต้องการ

ใช่การแปลงเป็นความรับผิดชอบอีกประการหนึ่ง มันไม่ได้ทำให้แตกต่างถ้าคุณทำผ่านคอนสตรัคเตอร์หรือวิธีการแปลง (เช่นToPerson)

การใช้วิธีการขยายในขณะที่มีการเข้าถึงรหัสต้นฉบับของคลาสถือว่าเป็นการปฏิบัติที่ไม่ดีหรือไม่?

ไม่จำเป็น. คุณสามารถสร้างวิธีการขยายแม้ว่าคุณจะมีซอร์สโค้ดของคลาสที่คุณต้องการขยาย ฉันคิดว่าไม่ว่าคุณจะสร้างวิธีการขยายหรือไม่ควรพิจารณาจากลักษณะของวิธีการดังกล่าว ตัวอย่างเช่นมันมีเหตุผลมากมายหรือไม่? มันขึ้นอยู่กับสิ่งอื่นใดที่สมาชิกของวัตถุเอง? ฉันจะบอกว่าคุณไม่ควรมีวิธีการขยายที่ต้องพึ่งพาการทำงานหรือที่มีตรรกะที่ซับซ้อน ตรรกะที่ง่ายที่สุดเท่านั้นที่ควรมีอยู่ในวิธีการขยาย

พฤติกรรมดังกล่าวสามารถใช้เป็นรูปแบบที่ทำงานได้สำหรับการแยกตรรกะหรือมันเป็นรูปแบบการต่อต้าน?

หากตรรกะนั้นซับซ้อนฉันคิดว่าคุณไม่ควรใช้วิธีการขยาย ดังที่ฉันได้กล่าวไว้ก่อนหน้านี้คุณควรใช้วิธีการขยายสำหรับสิ่งที่ง่ายที่สุดเท่านั้น ฉันจะไม่พิจารณาการเปลี่ยนแปลงที่ง่าย

ฉันขอแนะนำให้คุณสร้างบริการการแปลง คุณสามารถมีอินเทอร์เฟซทั่วไปแบบเดียวสำหรับสิ่งนี้:

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

และคุณสามารถมีตัวแปลงดังนี้

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

และคุณสามารถใช้การพึ่งพาการฉีดจะฉีดแปลง (เช่นIConverter<Person,Customer>) ในชั้นเรียนใด ๆ ที่ต้องใช้ความสามารถในการแปลงระหว่างและPersonCustomer


ถ้าฉันไม่เข้าใจผิดมีIConverterเฟรมเวิร์กอยู่แล้วรอการปรับใช้
RubberDuck

@RubberDuck มันอยู่ที่ไหน?
Yacoub Massad

ฉันกำลังคิดถึงIConvertableซึ่งไม่ใช่สิ่งที่เรากำลังมองหาที่นี่ ความผิดพลาดของฉัน.
RubberDuck

อ้า! ฉันพบสิ่งที่ฉันคิด @YacoubMassad Converterจะถูกใช้โดยเมื่อโทรList msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspxฉันไม่รู้ว่าเป็นประโยชน์กับ OP อย่างไร ConvertAll
RubberDuck

ยังมีความเกี่ยวข้อง มีคนอื่นใช้แนวทางที่คุณเสนอที่นี่ codereview.stackexchange.com/q/51889/41243
RubberDuck

5

การวางวิธีการแปลงภายในวัตถุ (POCO?) (เช่นเดียวกับ.ToPerson()วิธีการในชั้นเรียนผู้บริโภค) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดียวหรือไม่?

ใช่. Consumerระดับเป็นผู้รับผิดชอบสำหรับการถือครองข้อมูลที่เกี่ยวข้องกับผู้บริโภค (และอาจจะดำเนินการบางส่วน) และไม่ควรต้องรับผิดชอบสำหรับการแปลงตัวเองเข้าไปอีกชนิดที่ไม่เกี่ยวข้องกัน

การใช้คอนสตรัคเตอร์คอนสตรัคเตอร์ในคลาส (เหมือน DTO) ถือว่าเป็นการทำลายรูปแบบความรับผิดชอบเดี่ยวหรือไม่? โดยเฉพาะอย่างยิ่งหากคลาสดังกล่าวสามารถถูกแปลงจากแหล่งที่มาหลายประเภทดังนั้นคอนสตรัคเตอร์การแปลงหลายรายการจึงต้องการ

อาจ. ฉันมักจะมีวิธีนอกทั้งโดเมนและวัตถุ DTO เพื่อทำการแปลง ฉันมักจะใช้ที่เก็บข้อมูลที่ใช้ในวัตถุโดเมนและ (de) ทำให้เป็นอนุกรมพวกเขาไม่ว่าจะเป็นฐานข้อมูลหน่วยความจำไฟล์อะไรก็ตาม ถ้าฉันใส่ตรรกะนั้นลงในคลาสโดเมนของฉันตอนนี้ฉันได้เชื่อมโยงพวกเขาเข้ากับรูปแบบการทำให้เป็นอันดับแบบหนึ่งซึ่งไม่ดีสำหรับการทดสอบ (และสิ่งอื่น ๆ ) ถ้าฉันใส่ตรรกะในชั้นเรียน DTO ของฉันฉันผูกมันไว้กับโดเมนของฉันแล้ว จำกัด การทดสอบอีกครั้ง

การใช้วิธีการขยายในขณะที่มีการเข้าถึงรหัสต้นฉบับของคลาสถือว่าเป็นการปฏิบัติที่ไม่ดีหรือไม่?

ไม่ทั่วไปแม้ว่าจะสามารถใช้งานได้มากเกินไปก็ตาม ด้วยวิธีการขยายคุณสร้างตัวเลือกส่วนขยายที่รหัสทำให้ง่ายต่อการอ่าน พวกเขามีข้อเสียเปรียบ - มันง่ายกว่าที่จะสร้างความคลุมเครือที่คอมไพเลอร์ต้องแก้ไข (บางครั้งเงียบ ๆ ) และสามารถทำให้โค้ดยากขึ้นในการดีบัก

ในกรณีของคุณคลาสตัวแปลงหรือตัวทำแผนที่ดูเหมือนง่ายที่สุดและตรงไปตรงมาที่สุดถึงแม้ว่าฉันไม่คิดว่าคุณกำลังทำอะไรผิดโดยใช้วิธีการขยาย


2

วิธีการเกี่ยวกับการใช้ AutoMapper หรือ mapper ที่กำหนดเองสามารถใช้ Like ได้

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

จากโดเมน

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

ภายใต้ประทุนคุณสามารถย่อ AutoMapper หรือม้วน mapper ของคุณเอง


2

ฉันรู้ว่านี่เก่า แต่ก็ยังมีความสุข สิ่งนี้จะช่วยให้คุณแปลงทั้งสองวิธี ฉันพบว่ามีประโยชน์เมื่อทำงานกับ Entity Framework และการสร้าง 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
    }
}

ฉันพบว่า AutoMapper มีประโยชน์มากสำหรับการดำเนินการแมปที่เรียบง่ายจริงๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.