มีจุดใดบ้างในการใช้ตัวสร้างและส่วนต่อประสานกับวัตถุเริ่มต้น?


10

ใน Java และ C # คุณสามารถสร้างวัตถุที่มีคุณสมบัติที่สามารถตั้งค่าได้ที่การเริ่มต้นโดยการกำหนดตัวสร้างด้วยพารามิเตอร์การกำหนดแต่ละคุณสมบัติหลังจากการสร้างวัตถุหรือการใช้รูปแบบส่วนต่อประสานการสร้าง / ของเหลว อย่างไรก็ตาม C # 3 แนะนำวัตถุเริ่มต้นและคอลเลกชันซึ่งหมายความว่ารูปแบบการสร้างไม่ได้ผลส่วนใหญ่ ในภาษาที่ไม่มีค่าเริ่มต้นเราสามารถใช้เครื่องมือสร้างจากนั้นใช้มันเพื่อ:

Vehicle v = new Vehicle.Builder()
                    .manufacturer("Toyota")
                    .model("Camry")
                    .year(1997)
                    .colour(CarColours.Red)
                    .addSpecialFeature(new Feature.CDPlayer())
                    .addSpecialFeature(new Feature.SeatWarmer(4))
                    .build();

ในทางกลับกันใน C # one สามารถเขียน:

var vehicle = new Vehicle {
                Manufacturer = "Toyota",
                Model = "Camry",
                Year = 1997,
                Colour = CarColours.Red,
                SpecialFeatures = new List<SpecialFeature> {
                    new Feature.CDPlayer(),
                    new Feature.SeatWarmer { Seats = 4 }
                }
              }

... ไม่จำเป็นต้องมีผู้สร้างตามที่เห็นในตัวอย่างก่อนหน้านี้

จากตัวอย่างเหล่านี้ผู้สร้างยังคงมีประโยชน์ใน C # หรือถูกแทนที่โดยผู้เริ่มต้นทั้งหมดหรือไม่


are builders usefulเหตุใดฉันจึงเห็นคำถามประเภทเหล่านี้อยู่เรื่อย ๆ ตัวสร้างเป็นรูปแบบการออกแบบ - แน่นอนว่ามันอาจถูกเปิดเผยว่าเป็นคุณสมบัติทางภาษา แต่ในตอนท้ายของวันนั้นเป็นเพียงรูปแบบการออกแบบ รูปแบบการออกแบบไม่สามารถ "ผิด" มันอาจหรืออาจจะไม่เติมเต็มกรณีการใช้งานของคุณ แต่นั่นก็ไม่ได้ทำให้รูปแบบทั้งหมดผิด แต่เป็นการใช้งานของมัน โปรดจำไว้ว่าในตอนท้ายของรูปแบบการออกแบบในแต่ละวันจะแก้ปัญหาเฉพาะ - ถ้าคุณไม่ประสบปัญหาแล้วทำไมคุณต้องลองแก้ปัญหาด้วย?
VLAZ

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

3
@ vlaz ชี้แจง: ฉันกำลังบอกว่า initialisers ได้แทนที่ส่วนใหญ่ "ดั้งเดิม" ผู้สร้าง / รูปแบบอินเทอร์เฟซของของเหลวของการเขียนระดับตัวสร้างภายในจากนั้นเขียนวิธีการตั้งค่าการผูกมัดภายในชั้นเรียนซึ่งสร้างอินสแตนซ์ใหม่ของชั้นผู้ปกครองนั้น ฉันไม่ได้บอกว่าผู้เริ่มต้นแทนรูปแบบการสร้างเฉพาะนั้น ฉันกำลังบอกว่าผู้เริ่มต้นบันทึกนักพัฒนาที่ต้องใช้รูปแบบตัวสร้างเฉพาะนั้นบันทึกสำหรับกรณีการใช้งานที่กล่าวถึงในคำตอบซึ่งไม่ได้ทำให้รูปแบบตัวสร้างนั้นไร้ประโยชน์
svbnet

5
@ vlaz ฉันไม่เข้าใจความกังวลของคุณ "คุณกำลังบอกว่ารูปแบบการออกแบบไม่มีประโยชน์" - ไม่โจถามว่ารูปแบบการออกแบบนั้นมีประโยชน์หรือไม่ เป็นคำถามที่ไม่ถูกต้องผิดพลาดหรือผิดอย่างไร คุณคิดว่าคำตอบนั้นชัดเจนหรือไม่? ฉันไม่คิดว่าคำตอบนั้นชัดเจน นี่เป็นคำถามที่ดีสำหรับฉัน
Tanner Swett

2
@vlaz รูปแบบ singleton และ locator ของบริการผิด รูปแบบการออกแบบ Ergo อาจผิด
David Arno

คำตอบ:


12

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

รุ่นต่อไปของ C #, 8.0 มีแนวโน้มที่จะแนะนำwithคำหลักซึ่งจะช่วยให้วัตถุที่ไม่เปลี่ยนรูปแบบจะเริ่มต้นได้อย่างชัดเจนและรัดกุมโดยไม่จำเป็นต้องเขียนผู้สร้าง

สิ่งที่น่าสนใจอีกประการหนึ่งที่คุณสามารถทำได้กับผู้สร้างซึ่งต่างจาก initializers คือพวกมันสามารถส่งผลให้วัตถุประเภทต่าง ๆ ขึ้นอยู่กับลำดับของวิธีการที่เรียกว่า

ตัวอย่างเช่น

value.Match()
    .Case((DateTime d) => Console.WriteLine($"{d: yyyy-mm-dd}"))
    .Case((double d) => Console.WriteLine(Math.Round(d, 4));
    // void

var str = value.Match()
    .Case((DateTime d) => $"{d: yyyy-mm-dd}")
    .Case((double d) => Math.Round(d, 4).ToString())
    .ResultOrDefault(string.Empty);
    // string

เพียงเพื่อชี้แจงตัวอย่างข้างต้นมันเป็นห้องสมุดจับคู่รูปแบบที่ใช้รูปแบบการสร้างเพื่อสร้าง "จับคู่" โดยการระบุกรณี เคสจะถูกต่อท้ายโดยการเรียกCaseเมธอดที่ส่งผ่านฟังก์ชัน หากvalueสามารถกำหนดให้กับประเภทพารามิเตอร์ของฟังก์ชันมันจะถูกเรียกใช้ คุณสามารถค้นหาซอร์สโค้ดแบบเต็มบน GitHubและเนื่องจากความคิดเห็นของ XML ยากต่อการอ่านในข้อความธรรมดานี่คือลิงค์ไปยังเอกสารประกอบของ SandCastle (ดูหัวข้อข้อสังเกต )


ฉันไม่เห็นวิธีการนี้ไม่สามารถทำได้ในการเริ่มต้นวัตถุโดยใช้IEnumerable<Func<T, TResult>>สมาชิก
Caleth

@ Caleth นั่นเป็นสิ่งที่ฉันทดลอง แต่มีบางประเด็นเกี่ยวกับวิธีการนั้น มันไม่ได้อนุญาตให้มีคำสั่งเงื่อนไข (ไม่ปรากฏ แต่ใช้และแสดงให้เห็นในเอกสารที่เชื่อมโยง) TResultและจะไม่อนุญาตให้มีการอนุมานชนิดของ การอนุมานประเภทท้ายที่สุดมีส่วนเกี่ยวข้องกับมันมาก ฉันอยากให้มัน "ดูเหมือน" เหมือนโครงสร้างควบคุม นอกจากนี้ฉันไม่ต้องการใช้เครื่องมือกำหนดค่าเริ่มต้นเนื่องจากจะอนุญาตการกลายพันธุ์
Aluan Haddad

12

วัตถุเริ่มต้นต้องการให้คุณสมบัติต้องสามารถเข้าถึงได้โดยการเรียกรหัส ผู้สร้างที่ซ้อนกันอาจเข้าถึงสมาชิกส่วนตัวของชั้นเรียน

หากคุณต้องการทำให้Vehicleไม่เปลี่ยนรูปแบบ (ผ่านการตั้งค่าส่วนตัวทั้งหมด) จากนั้นตัวสร้างที่ซ้อนกันสามารถใช้เพื่อตั้งค่าตัวแปรส่วนตัว


0

พวกเขาทั้งหมดมีจุดประสงค์ที่แตกต่าง !!!

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

Initializersทำงานหลังจากการก่อสร้าง พวกเขาให้คุณเรียกคุณสมบัติสาธารณะเท่านั้น ไม่สามารถตั้งค่าฟิลด์ส่วนตัวและ / หรือแบบอ่านอย่างเดียวได้ จากการประชุมการกระทำของการตั้งค่าคุณสมบัติควรจะค่อนข้าง จำกัด เช่นควรเป็น idempotent และมีผลข้างเคียงที่ จำกัด

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

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