การใช้ตัวดำเนินการแคสต์อย่างชัดเจนของฉันนั้นสมเหตุสมผลหรือแฮ็คที่ไม่ดี?


24

ฉันมีวัตถุขนาดใหญ่:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

และวัตถุที่เหมือน DTO เฉพาะด้าน:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

ฉันพบแนวคิดส่วนตัวในการส่ง BigObject ไปยัง SmallObject อย่างชัดเจนโดยรู้ว่าเป็นการดำเนินการทางเดียวที่ทำให้ข้อมูลสูญหาย - ใช้งานง่ายและอ่านได้ง่ายมาก:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

มันถูกใช้งานโดยใช้โอเปอเรเตอร์ที่ชัดเจน:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

ตอนนี้ฉันสามารถสร้างSmallObjectFactoryชั้นเรียนด้วยFromBigObject(BigObject big)วิธีการที่จะทำสิ่งเดียวกันเพิ่มไปยังการฉีดพึ่งพาและเรียกมันว่าเมื่อจำเป็น ... แต่สำหรับฉันดูเหมือนว่ายิ่งซับซ้อนและไม่จำเป็น

ป.ล. ฉันไม่แน่ใจว่าสิ่งนี้เกี่ยวข้องหรือไม่ แต่จะมีสิ่งใดบ้างOtherBigObjectที่สามารถเปลี่ยนเป็นSmallObjectตั้งค่าต่างEnumTypeๆ ได้


4
ทำไมไม่สร้างหรือไม่
edc65

2
หรือวิธีการโรงงานคงที่?
Brian Gordon

ทำไมคุณต้องเรียนในระดับโรงงานหรือการฉีดพึ่งพา? คุณทำขั้วคู่ผิดที่นั่น
253751

1
@immibis - เพราะอย่างใดฉันไม่ได้คิดเกี่ยวกับสิ่งที่ @Tastastyn เสนอ: .ToSmallObject()วิธีการ (หรือGetSmallObject()) เมื่อเวลาผ่านไปด้วยเหตุผล - ฉันรู้ว่ามีบางอย่างผิดปกติกับความคิดของฉันดังนั้นฉันจึงถามพวกคุณ :)
Gerino

3
นี่เป็นกรณีการใช้งานที่สมบูรณ์แบบสำหรับอินเทอร์เฟซ ISmallObject ซึ่งดำเนินการโดย BigObject เท่านั้นซึ่งเป็นวิธีการในการเข้าถึงชุดข้อมูล / พฤติกรรมที่ จำกัด โดยเฉพาะอย่างยิ่งเมื่อรวมกับแนวคิดของ @ Telastyn เกี่ยวกับToSmallObjectวิธีการ
Marjan Venema

คำตอบ:


0

ไม่มีคำตอบอื่นใดที่ถูกต้องในความคิดที่ต่ำต้อยของฉัน ในคำถาม stackoverflowนี้คำตอบที่ได้รับการโหวตสูงสุดระบุว่ารหัสการจับคู่ควรเก็บไว้นอกโดเมน เพื่อตอบคำถามของคุณไม่ได้ - การใช้งานตัวดำเนินการส่งของคุณไม่ดีนัก ฉันขอแนะนำให้ทำแผนที่บริการซึ่งตั้งอยู่ระหว่าง DTO ของคุณกับคุณโดเมนวัตถุหรือคุณสามารถใช้ automapper สำหรับสิ่งนั้น


นี่เป็นความคิดที่ยอดเยี่ยม ฉันมี Automapper อยู่แล้วดังนั้นมันจึงเป็นเรื่องง่าย ปัญหาเดียวที่ฉันมี: ไม่ควรมีร่องรอยบางอย่างที่ BigObject และ SmallObject เกี่ยวข้องกัน?
Gerino

1
ไม่ฉันไม่เห็นข้อได้เปรียบใด ๆ จากการเชื่อมต่อ BigObject และ SmallObject เข้าด้วยกันนอกเหนือจากบริการการทำแผนที่
Esben Skov Pedersen

7
จริงๆ? automapper คือทางออกสำหรับปัญหาการออกแบบหรือไม่?
Telastyn

1
BigObject สามารถแมปกับ SmallObject ได้ซึ่งไม่เกี่ยวข้องกันในความรู้สึกแบบ OOP แบบดั้งเดิมและรหัสนี้สะท้อนให้เห็นถึง (วัตถุทั้งสองมีอยู่ในโดเมน มันจะลบโค้ดที่น่าสงสัย (การลบโอเปอเรเตอร์ที่โชคร้ายของฉัน) มันทำให้โมเดลสะอาด
Gerino

2
@EsbenSkovPedersen โซลูชันนี้เปรียบเสมือนการใช้รถปราบดินเพื่อขุดหลุมเพื่อติดตั้งกล่องจดหมายของคุณ โชคดีที่ OP ต้องการขุดหลาดังนั้นรถปราบดินจึงทำงานในกรณีนี้ แต่ผมไม่อยากจะแนะนำการแก้ปัญหานี้โดยทั่วไป
Neil

81

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

ฉันอยากจะแนะนำ.ToSmallObject()วิธีการแทน มันชัดเจนเกี่ยวกับสิ่งที่เกิดขึ้นจริงและเป็น verbose


18
Doh ... ToSmallObject () ดูเหมือนจะเป็นตัวเลือกที่ชัดเจนที่สุด บางครั้งชัดเจนที่สุดคือเข้าใจยากมากที่สุด)
Gerino

6
mildly distastefulเป็นการพูดน้อย เป็นเรื่องน่าเสียดายที่ภาษานี้อนุญาตให้ดูเหมือนประเภท typecast ไม่มีใครคิดว่ามันเป็นการเปลี่ยนแปลงวัตถุจริงเว้นแต่พวกเขาจะเขียนเอง ในทีมคนเดียวก็ได้ หากคุณร่วมมือกับใครก็ตามในกรณีที่ดีที่สุดมันเสียเวลาเพราะคุณต้องหยุดและคิดออกว่ามันเป็นนักแสดงจริงๆหรือถ้ามันเป็นหนึ่งในการแปลงที่บ้าคลั่ง
Kent A.

3
@Tastastyn ตกลงกันว่ามันไม่ได้มีกลิ่นรหัสที่ร้ายแรงที่สุด แต่การสร้างวัตถุใหม่ที่ซ่อนอยู่จากการทำงานที่โปรแกรมเมอร์ส่วนใหญ่เข้าใจว่าเป็นคำสั่งให้คอมไพเลอร์ในการรักษาวัตถุเดียวกันนั้นเป็นประเภทที่แตกต่างกันนั้นจะไม่เป็นการแสดงความเมตตาต่อผู้ที่ต้องทำงานกับรหัสของคุณหลังจากคุณ :)
Kent A.

4
+1 .ToSmallObject()สำหรับ คุณไม่ควรแทนที่โอเปอเรเตอร์เลยทีเดียว
ytoledano

6
@dorus - อย่างน้อยใน. NET Getหมายถึงการคืนสิ่งที่มีอยู่ หากคุณไม่ได้แทนที่การดำเนินการกับวัตถุขนาดเล็กการGetโทรสองครั้งจะส่งคืนวัตถุที่ไม่เท่ากันทำให้เกิดความสับสน / บั๊ก / wtfs
Telastyn

11

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

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

หมายเหตุวิธีนี้BigObjectไม่ได้ขึ้นอยู่กับSmallObjectแต่วิธีอื่น ๆ (ตามที่ควรเป็นในความเห็นต่ำต้อยของฉัน)


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

@Doval ฉันคิดว่าเป็นประเด็น คุณจะไม่แปลงเป็นคลาสใหม่ คุณจะสร้างซุ้มอื่นถ้านั่นคือสิ่งที่คุณต้องการ การเปลี่ยนแปลงที่ทำกับ BigObject จำเป็นต้องใช้กับคลาส Facade เท่านั้นและไม่ได้มีอยู่ทุกที่ที่ใช้
Neil

หนึ่งความแตกต่างที่น่าสนใจระหว่างวิธีการนี้และคำตอบของ Telastynเป็นความรับผิดชอบไม่ว่าจะเป็นสำหรับการสร้างSmallObjectการโกหกด้วยหรือSmallObject BigObjectโดยค่าเริ่มต้นนี้กองกำลังวิธี SmallObjectเพื่อหลีกเลี่ยงการอ้างอิงในส่วนตัวของสมาชิก / BigObjectป้องกัน เราสามารถไปอีกขั้นหนึ่งและหลีกเลี่ยงการพึ่งพาสมาชิกส่วนตัว / ที่ได้รับการป้องกันSmallObjectโดยใช้ToSmallObjectวิธีการขยาย
Brian

@Brian คุณมีความเสี่ยงBigObjectอย่างนั้น หากคุณต้องการทำสิ่งที่คล้ายกันคุณจะรับรองToAnotherObjectวิธีสร้างส่วนขยายภายในBigObjectหรือไม่ สิ่งเหล่านี้ไม่ควรกังวลBigObjectตั้งแต่นั้นมามันมีขนาดใหญ่พออยู่แล้ว นอกจากนี้ยังช่วยให้คุณแยกออกBigObjectจากการสร้างการอ้างอิงซึ่งหมายความว่าคุณสามารถใช้โรงงานและสิ่งที่ชอบ วิธีการอื่น ๆ ขอคู่สมรสและBigObject SmallObjectอาจเป็นเรื่องปกติในกรณีนี้ แต่มันก็ไม่ใช่วิธีปฏิบัติที่ดีที่สุดในความเห็นของฉัน
Neil

1
@Neil จริงๆแล้ว Brian อธิบายผิด แต่เขาก็พูดถูก - วิธีการต่อพ่วงกำจัดข้อต่อออก มันไม่BigObjectถูกคู่กับSmallObjectมันเป็นเพียงวิธีการที่ใดที่หนึ่งคงที่จะโต้แย้งของและผลตอบแทนBigObject SmallObjectวิธีการขยายจริง ๆ แล้วเป็นเพียงน้ำตาล syntactic เพื่อเรียกวิธีการคงที่ในทางที่ดีกว่า วิธีการขยายไม่ได้เป็นส่วนหนึ่งของBigObjectมันเป็นวิธีคงที่แยกต่างหากอย่างสมบูรณ์ จริงๆแล้วมันเป็นวิธีการใช้งานที่ดีมากและมีประโยชน์มากสำหรับการแปลง DTO โดยเฉพาะ
Luaan

6

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

ฉันขอแนะนำให้เป็นข้อกำหนดที่ให้x=(SomeType)foo;ตามมาในภายหลังy=(SomeType)foo;ด้วยทั้ง casts ที่นำไปใช้กับวัตถุเดียวกันx.Equals(y)ควรเสมอและตลอดไปจะเป็นจริงแม้ว่าวัตถุที่มีปัญหาจะได้รับการแก้ไขระหว่างสอง casts สถานการณ์ดังกล่าวสามารถนำไปใช้ได้หากเช่นมีวัตถุคู่หนึ่งชนิดที่แตกต่างกันซึ่งแต่ละรายการมีการอ้างอิงที่ไม่เปลี่ยนรูปแบบไปยังอีกอันหนึ่งและการส่งวัตถุทั้งสองไปที่อีกประเภทหนึ่งจะส่งคืนอินสแตนซ์ที่จับคู่ นอกจากนี้ยังสามารถนำไปใช้กับประเภทที่ทำหน้าที่เป็นตัวห่อกับวัตถุที่ไม่แน่นอนได้หากว่าตัวตนของวัตถุที่ถูกห่อเป็นสิ่งที่ไม่สามารถเปลี่ยนแปลงได้และสิ่งห่อหุ้มสองประเภทที่เหมือนกันจะรายงานตัวเองว่าเท่าเทียมกัน

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


1

มันอาจจะไม่เป็นไร

ปัญหาเกี่ยวกับตัวอย่างของคุณคือคุณใช้ชื่อ example-ish ดังกล่าว พิจารณา:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

ตอนนี้เมื่อคุณได้ความคิดที่ดีสิ่งที่ยาวและintวิธีแล้วทั้งหล่อโดยนัยของintการlongและนักแสดงอย่างชัดเจนจากlongการintเป็นที่เข้าใจมาก นอกจากนี้ยังเป็นที่เข้าใจวิธีการที่3จะกลายเป็นและเป็นเพียงวิธีการทำงานอีกด้วย3 3เป็นที่เข้าใจได้ว่ามันจะล้มเหลวอย่างไรint.MaxValue + 1ในบริบทที่ตรวจสอบ แม้ว่ามันจะทำงานกับint.MaxValue + 1บริบทที่ไม่ได้ตรวจสอบเพื่อให้ได้ผลลัพธ์int.MinValueไม่ใช่สิ่งที่ยากที่สุดในการติดตาม

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

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


อันที่จริงพวกเขาไม่ได้มากไปกว่าสิ่งที่ระบุไว้ในคำถาม - แต่สำหรับตัวอย่างเช่นBigObjectอาจจะอธิบายEmployee {Name, Vacation Days, Bank details, Access to different building floors etc.}และอาจจะมีการSmallObject MoneyTransferRecepient {Name, Bank details}มีการแปลตรงไปตรงมาจากเป็นEmployeeไปMoneyTransferRecepientและมีเหตุผลที่จะส่งไปยังโปรแกรมประยุกต์ธนาคารข้อมูลใด ๆ ที่เกินความจำเป็นใด ๆ
Gerino
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.