เมื่อใดที่การทดสอบประเภทตกลง


53

การสมมติภาษาที่มีความปลอดภัยบางประเภท (เช่นไม่ใช่จาวาสคริปต์):

ด้วยวิธีการที่ยอมรับได้SuperTypeเรารู้ว่าในกรณีส่วนใหญ่นั้นเราอาจถูกล่อลวงให้ทำการทดสอบประเภทเพื่อเลือกการกระทำ:

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    o.doSomethingA()
  } else {
    o.doSomethingB();
  }
}

โดยปกติเราควรสร้างวิธีการ overridable เดียวSuperTypeและไม่ควรทำเช่นนี้เสมอ:

public void DoSomethingTo(SuperType o) {
  o.doSomething();
}

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

ยอดเยี่ยม

แต่เรายังคงได้รับis aการปฏิบัติเหมือนในภาษาส่วนใหญ่ถ้าไม่ใช่ทั้งหมด และนั่นแสดงให้เห็นถึงความต้องการที่อาจเกิดขึ้นสำหรับการทดสอบประเภทที่ชัดเจน

ดังนั้นในสถานการณ์ใดบ้างถ้ามีควรเราหรือต้องเราดำเนินการทดสอบประเภทอย่างชัดเจน?

ให้อภัยการขาดสติหรือขาดความคิดสร้างสรรค์ ฉันรู้ว่าฉันเคยทำมาก่อน แต่เมื่อนานมาแล้วฉันจำไม่ได้ว่าสิ่งที่ฉันทำดีหรือไม่! และในความทรงจำเมื่อเร็ว ๆ นี้ฉันไม่คิดว่าฉันจะต้องทดสอบประเภทนอกจาวาสคริปต์ของฉัน


1
อ่านเกี่ยวกับการอนุมานประเภทและระบบการพิมพ์
Basile Starynkevitch

4
เป็นมูลค่าชี้ให้เห็นว่าทั้ง Java และ C # มี generics ในรุ่นแรกของพวกเขา คุณต้องส่งไปยังและจากออบเจ็กต์เพื่อใช้คอนเทนเนอร์
Doval

6
"การตรวจสอบประเภท" มักจะหมายถึงการตรวจสอบว่ารหัสสังเกตกฎการพิมพ์แบบคงที่ของภาษาหรือไม่ สิ่งที่คุณหมายมักจะเรียกว่าประเภทการทดสอบ


3
นี่คือเหตุผลที่ฉันชอบเขียน C ++ และปิด RTTI ทิ้ง เมื่อหนึ่งไม่สามารถทดสอบชนิดของวัตถุที่รันไทม์อย่างแท้จริงมันบังคับให้นักพัฒนายึดมั่นในการออกแบบ OO ที่ดีเกี่ยวกับคำถามที่ถูกถามที่นี่

คำตอบ:


48

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

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

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

วิธีแก้ปัญหา: ใช้สำนวน ตามที่คุณแนะนำให้เพิ่มเมธอดให้กับแต่ละคลาสจากนั้นให้ใช้การสืบทอดมาตรฐานและอัลกอริธึมการเลือกเมธอดเพื่อกำหนดกรณีและปัญหา หรือถ้าคุณไม่สามารถเปลี่ยนประเภทฐาน, คลาสย่อยและเพิ่มวิธีการของคุณที่นั่น

มากสำหรับภูมิปัญญาดั้งเดิมและคำตอบบางอย่าง บางกรณีที่การทดสอบประเภทที่ชัดเจนมีเหตุผล:

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

  2. คุณไม่สามารถปรับคลาสได้ คุณคิดถึงคลาสย่อย - แต่คุณทำไม่ได้ หลายชั้นเรียนใน Java finalตัวอย่างเช่นมีการกำหนด คุณพยายามที่จะโยนpublic class ExtendedSubTypeA extends SubTypeA {...} และคอมไพเลอร์บอกคุณในแง่ที่ไม่แน่นอนว่าสิ่งที่คุณทำไม่ได้ ขออภัยความสง่างามและความซับซ้อนของแบบจำลองเชิงวัตถุ! มีคนตัดสินใจว่าคุณไม่สามารถขยายประเภทของพวกเขาได้! น่าเสียดายที่ห้องสมุดมาตรฐานหลายแห่งfinalและการสร้างคลาสfinalเป็นแนวทางการออกแบบทั่วไป ฟังก์ชั่น end-run คือสิ่งที่เหลืออยู่สำหรับคุณ

    BTW นี่ไม่ได้ จำกัด เฉพาะภาษาที่พิมพ์แบบคงที่ ภาษาไดนามิก Python มีคลาสพื้นฐานจำนวนหนึ่งซึ่งไม่สามารถแก้ไขได้ภายใต้การครอบคลุมที่ใช้ใน C เช่นเดียวกับ Java ซึ่งมีประเภทมาตรฐานเกือบทั้งหมด

  3. รหัสของคุณภายนอก คุณกำลังพัฒนาด้วยคลาสและวัตถุที่มาจากช่วงของเซิร์ฟเวอร์ฐานข้อมูลมิดเดิลแวร์เอ็นจิ้นและเบสโค้ดอื่น ๆ ที่คุณไม่สามารถควบคุมหรือปรับได้ รหัสของคุณเป็นเพียงวัตถุที่ถูกสร้างขึ้นที่อื่น แม้ว่าคุณจะสามารถคลาสย่อยSuperTypeคุณจะไม่สามารถรับไลบรารี่เหล่านั้นที่คุณต้องพึ่งพาเพื่อสร้างออบเจ็กต์ในคลาสย่อยของคุณ พวกเขาจะส่งอินสแตนซ์ของประเภทที่คุณรู้ไม่ใช่ตัวแปรของคุณ นี่ไม่ใช่กรณี ... บางครั้งพวกเขาสร้างขึ้นเพื่อความยืดหยุ่นและพวกเขายกตัวอย่างอินสแตนซ์ของชั้นเรียนที่คุณฟีดพวกเขาแบบไดนามิก หรือพวกเขามีกลไกในการลงทะเบียนคลาสย่อยที่คุณต้องการให้โรงงานสร้าง ตัวแยกวิเคราะห์ XML ดูดีเป็นพิเศษที่ให้จุดเข้าใช้งานดังกล่าว ดูเช่น หรือlxml ในหลาม แต่ฐานรหัสส่วนใหญ่ไม่ได้ ให้บริการส่วนขยายดังกล่าว พวกเขาจะส่งคุณกลับไปเรียนที่พวกเขาสร้างขึ้นด้วยและรู้เกี่ยวกับ โดยทั่วไปแล้วจะไม่เหมาะสมที่จะใช้พร็อกซีผลลัพธ์ในผลลัพธ์ที่กำหนดเองของคุณเพื่อให้คุณสามารถใช้ตัวเลือกชนิดของวัตถุ หากคุณกำลังจะทำการเลือกปฏิบัติประเภทคุณจะต้องทำมันค่อนข้างหยาบ รหัสทดสอบประเภทของคุณนั้นค่อนข้างเหมาะสม

  4. ข้อมูลทั่วไปของบุคคลผู้น่าสงสาร / การส่งหลายครั้ง คุณต้องการยอมรับประเภทที่แตกต่างหลากหลายให้กับรหัสของคุณและรู้สึกว่าการมีวิธีการเฉพาะแบบหลายประเภทนั้นไม่ได้สวยงาม public void add(Object x)ดูเหมือนว่าตรรกะ แต่ไม่อาร์เรย์ของ addByte, addShort, addInt, addLong, addFloat, addDouble, addBoolean, addCharและaddStringสายพันธุ์ (เพื่อชื่อไม่กี่) การมีฟังก์ชั่นหรือวิธีการที่ให้ผลตอบแทนสูงเป็นพิเศษแล้วพิจารณาว่าจะทำอย่างไรในแบบทีละประเภท - พวกเขาจะไม่ได้รับรางวัล Purity Award ในงาน Booch-Liskov Design Symposium ประจำปีการตั้งชื่อภาษาฮังการี จะให้ API ที่ง่ายขึ้น ในความรู้สึกของคุณis-aหรือis-instance-of การทดสอบเป็นการจำลองแบบทั่วไปหรือหลายการจัดส่งในบริบทภาษาที่ไม่สนับสนุน

    การสนับสนุนภาษาในตัวสำหรับทั้งชื่อ สามัญและการ พิมพ์เป็ดลดความจำเป็นในการตรวจสอบประเภทโดยการ "ทำสิ่งที่สง่างามและเหมาะสม" มีแนวโน้มมากขึ้น การเลือกการจัดส่ง / อินเทอร์เฟซหลายอย่างที่เห็นในภาษาเช่น Julia และ Go ในทำนองเดียวกันแทนที่การทดสอบประเภทโดยตรงด้วยกลไกในตัวสำหรับการเลือกประเภทที่ใช้ "สิ่งที่ต้องทำ" แต่ไม่ใช่ทุกภาษาที่รองรับสิ่งเหล่านี้ Java เช่นโดยทั่วไปแล้วส่งแบบเดียวและสำนวนที่ไม่เป็นมิตรกับการพิมพ์เป็ด

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


หากการดำเนินการไม่สามารถทำได้กับประเภทเฉพาะ แต่สามารถดำเนินการกับสองประเภทที่แตกต่างกันซึ่งมันจะแปลงได้โดยปริยายการบรรทุกเกินพิกัดมีความเหมาะสมก็ต่อเมื่อทั้งสองการแปลงจะให้ผลลัพธ์ที่เหมือนกัน มิฉะนั้นจะจบลงด้วยพฤติกรรม crummy เช่นใน. NET: (1.0).Equals(1.0f)ยอมจริง [โต้แย้งส่งเสริมเพื่อdouble] แต่(1.0f).Equals(1.0)ให้ผลเท็จ [โต้แย้งส่งเสริมเพื่อobject]; ใน Java ให้Math.round(123456789*1.0)ผลตอบแทน 123456789 แต่Math.round(123456789*1.0)ให้ผลตอบแทน 123456792 [อาร์กิวเมนต์ส่งเสริมให้floatมากกว่าdouble]
supercat

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

ฉันตอบสนองต่อประเด็นของคุณ # 4 ซึ่งดูเหมือนว่าจะสนับสนุนการรับน้ำหนักมากเกินแทนที่จะใช้วิธีการที่แตกต่างกันที่มีชื่อแตกต่างกัน
supercat

2
@supercat ตำหนิการมองเห็นที่ไม่ดีของฉัน แต่การแสดงออกทั้งสองนั้นMath.roundดูเหมือนกับฉัน ความแตกต่างคืออะไร?
Lily Chung

2
@IstvanChung: อุ๊ปส์ ... อันหลังควรจะเป็นMath.round(123456789)[บ่งบอกถึงสิ่งที่อาจเกิดขึ้นได้ถ้ามีคนเขียนซ้ำMath.round(thing.getPosition() * COUNTS_PER_MIL)เพื่อส่งคืนค่าตำแหน่งที่ไม่ได้ปรับสัดส่วนไม่ทราบว่าgetPositionคืนintหรือหรือlong]]
supercat

25

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

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

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


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

1
การทำให้เป็นอันดับมักจะทำได้โดยใช้ความหลากหลาย เมื่อ deserializing คุณมักจะทำสิ่งที่ชอบBaseClass base = deserialize(input)เพราะคุณยังไม่รู้ประเภทแล้วคุณif (base instanceof Derived) derived = (Derived)baseจะเก็บมันเป็นประเภทที่ได้รับมาแน่นอน
Karl Bielefeldt

1
ความเท่าเทียมทำให้รู้สึก จากประสบการณ์ของฉันวิธีการดังกล่าวมักจะมีรูปแบบเช่น“ ถ้าวัตถุทั้งสองนี้มีรูปธรรมเหมือนกันให้คืนค่าว่าเขตข้อมูลทั้งหมดนั้นเท่ากันหรือไม่ มิฉะนั้นส่งคืน false (หรือไม่สามารถเทียบเคียงได้)”
Jon Purdy

2
โดยเฉพาะอย่างยิ่งความจริงที่ว่าคุณพบว่าตัวคุณเองประเภทการทดสอบเป็นตัวบ่งชี้ที่ดีว่าการเปรียบเทียบความเท่าเทียมกันแบบ polymorphic เป็นรังของงูพิษ
Steve Jessop

12

กรณีมาตรฐาน (แต่หายากหวังว่า) จะมีลักษณะเช่นนี้: หากอยู่ในสถานการณ์ต่อไปนี้

public void DoSomethingTo(SuperType o) {
  if (o isa SubTypeA) {
    DoSomethingA((SubTypeA) o )
  } else {
    DoSomethingB((SubTypeB) o );
  }
}

ฟังก์ชั่นDoSomethingAหรือDoSomethingBไม่สามารถจะนำมาใช้เป็นฟังก์ชั่นสมาชิกของต้นไม้มรดกของSuperType/ /SubTypeA SubTypeBตัวอย่างเช่นถ้า

  • ชนิดย่อยนั้นเป็นส่วนหนึ่งของไลบรารีที่คุณไม่สามารถเปลี่ยนแปลงได้หรือ
  • หากเพิ่มรหัสสำหรับDoSomethingXXXห้องสมุดนั้นจะหมายถึงการแนะนำการพึ่งพาที่ต้องห้าม

โปรดทราบว่ามักมีสถานการณ์ที่คุณสามารถหลีกเลี่ยงปัญหานี้ได้ (ตัวอย่างเช่นโดยการสร้าง wrapper หรืออะแดปเตอร์สำหรับSubTypeAและSubTypeBหรือพยายามนำกลับมาใช้ใหม่อย่างDoSomethingสมบูรณ์ในแง่ของการดำเนินงานพื้นฐานSuperType) แต่บางครั้งโซลูชันเหล่านี้ไม่คุ้มค่ากับความยุ่งยากหรือ สิ่งที่ซับซ้อนและยืดออกน้อยกว่าทำแบบทดสอบที่ชัดเจน

ตัวอย่างจากการทำงานเมื่อวานของฉัน: ฉันมีสถานการณ์ที่ฉันจะทำการประมวลผลรายการวัตถุแบบขนาน (ชนิดSuperTypeกับชนิดย่อยที่แตกต่างกันสองแบบซึ่งมันไม่น่าเป็นไปได้อย่างยิ่งที่จะมีอีกมาก) รุ่น unparallelized มีสองวงหนึ่งห่วงสำหรับวัตถุชนิดย่อยเรียกDoSomethingAและวงที่สองสำหรับวัตถุชนิดย่อย B DoSomethingBเรียก

วิธีการ "DoSomethingA" และ "DoSomethingB" นั้นมีทั้งการคำนวณแบบเร่งรัดเวลาโดยใช้ข้อมูลบริบทซึ่งไม่สามารถใช้งานได้ในขอบเขตของชนิดย่อย A และ B (ดังนั้นจึงไม่มีเหตุผลที่จะนำไปใช้เป็นฟังก์ชันสมาชิกของชนิดย่อย) จากมุมมองของ "ลูปขนาน" ใหม่มันทำให้สิ่งต่าง ๆ ง่ายขึ้นมากโดยการจัดการกับมันอย่างสม่ำเสมอดังนั้นฉันจึงใช้ฟังก์ชั่นที่คล้ายกับDoSomethingToจากด้านบน อย่างไรก็ตามการดูการใช้งานของ "DoSomethingA" และ "DoSomethingB" แสดงให้เห็นว่าพวกเขาทำงานภายในแตกต่างกันมาก ดังนั้นการพยายามใช้ "DoSomething" ทั่วไปโดยการขยายSuperTypeด้วยวิธีการที่เป็นนามธรรมจำนวนมากนั้นไม่ได้ผลจริง ๆ หรือจะหมายถึงการออกแบบสิ่งต่าง ๆ อย่างสมบูรณ์


2
โอกาสใด ๆ ที่คุณสามารถเพิ่มตัวอย่างเล็ก ๆ ที่เป็นรูปธรรมเพื่อโน้มน้าวใจสมองที่เต็มไปด้วยหมอกของฉันสถานการณ์นี้ไม่ได้ถูกประดิษฐ์ขึ้นมา?
svidgen

เพื่อความชัดเจนฉันไม่ได้พูดว่ามันถูกออกแบบมา ฉันสงสัยว่าวันนี้ฉันมีความผิดปกติอย่างมาก
svidgen

@svidgen: สิ่งนี้อยู่ไกลจาก beeing contrived จริง ๆ แล้วฉันพบสถานการณ์เหล่านี้ในวันนี้ (แม้ว่าฉันไม่สามารถโพสต์ตัวอย่างนี้ได้ที่นี่เพราะมันมีธุรกิจภายใน) และฉันเห็นด้วยกับคำตอบอื่น ๆ ว่าการใช้ตัวดำเนินการ "is" ควรเป็นข้อยกเว้นและทำได้เฉพาะในกรณีที่หายาก
Doc Brown

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

6
นี่คือตัวอย่างที่เป็นรูปธรรม: คอนเทนเนอร์ระดับบนสุดในการตอบสนอง JSON จากบริการบนเว็บสามารถเป็นได้ทั้งพจนานุกรมหรืออาร์เรย์ โดยทั่วไปคุณมีเครื่องมือบางอย่างที่เปลี่ยน JSON ให้เป็นวัตถุจริง (เช่นNSJSONSerializationใน Obj-C) แต่คุณไม่ต้องการเพียงแค่เชื่อมั่นว่าการตอบสนองมีประเภทที่คุณคาดไว้ดังนั้นก่อนที่จะใช้คุณตรวจสอบมัน (เช่นif ([theResponse isKindOfClass:[NSArray class]])...) .
คาเลบ

5

ในฐานะที่ลุงบ๊อบเรียกมันว่า:

When your compiler forgets about the type.

ในตอนหนึ่งของ Clean Coder ตอนเขายกตัวอย่างของการเรียกฟังก์ชันที่ใช้เพื่อส่งคืนEmployees เป็นชนิดย่อยของManager Employeeสมมติว่าเรามีบริการแอปพลิเคชั่นที่ยอมรับManagerรหัสและเรียกให้เขาทำงาน :) ฟังก์ชันgetEmployeeById()คืนค่า super-type Employeeแต่ฉันต้องการตรวจสอบว่ามีการส่งคืนผู้จัดการในกรณีใช้นี้หรือไม่

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

var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
   throw new Exception("Invalid Id specified.");
manager.summon();

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

ไม่ใช่ตัวอย่างที่ดีที่สุด แต่เป็นลุงบ๊อบหลังจากทั้งหมด

ปรับปรุง

ฉันอัปเดตตัวอย่างมากเท่าที่ฉันจำได้จากหน่วยความจำ


1
เหตุใดManagerการปรับใช้ของsummon()ข้อยกเว้นในตัวอย่างนี้จึงไม่ได้ผล
svidgen

@svidgen บางทีCEOสามารถเรียกManagers
user253751

@svidgen แล้วมันจะไม่ชัดเจนว่าคาดว่า employeeRepository.getEmployeeById (empId) คาดว่าจะส่งคืนผู้จัดการ
เอียน

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

3

การตรวจสอบประเภทตกลงเมื่อใด

ไม่เคย

  1. เมื่อมีพฤติกรรมที่เกี่ยวข้องกับประเภทนั้นในฟังก์ชั่นอื่น ๆ คุณกำลังละเมิดหลักการ Open Openเนื่องจากคุณสามารถแก้ไขพฤติกรรมที่มีอยู่ของประเภทนั้นได้โดยการเปลี่ยนisประโยคหรือ (ในบางภาษาหรือขึ้นอยู่กับ สถานการณ์) เนื่องจากคุณไม่สามารถขยายประเภทโดยไม่ต้องแก้ไข internals ของฟังก์ชันที่ทำการisตรวจสอบ
  2. ที่สำคัญกว่าisการตรวจสอบเป็นสัญญาณที่แข็งแกร่งที่คุณกำลังละเมิดLiskov ชดเชยหลักการ สิ่งใดที่ทำงานร่วมกับSuperTypeควรจะไม่รู้สิ่งที่อาจมีประเภทย่อย
  3. คุณเชื่อมโยงพฤติกรรมบางอย่างกับชื่อประเภทโดยปริยาย สิ่งนี้ทำให้รหัสของคุณยากขึ้นเนื่องจากสัญญาเหล่านี้มีการแพร่กระจายทั่วทั้งรหัสและไม่รับประกันว่าจะมีการบังคับใช้ในระดับสากลและสม่ำเสมอเช่นเดียวกับสมาชิกชั้นเรียนจริง

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

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


3
มีข้อแม้เล็กน้อยหนึ่งข้อ: การใช้ประเภทข้อมูลเกี่ยวกับพีชคณิตในภาษาที่ไม่รองรับ อย่างไรก็ตามการใช้งานของการสืบทอดและการพิมพ์ดีดนั้นเป็นรายละเอียดของการแฮ็กข้อมูลอย่างละเอียด ความตั้งใจไม่ได้แนะนำประเภทย่อย แต่เพื่อจัดหมวดหมู่ค่า ฉันนำมาใช้เพราะ ADT มีประโยชน์และไม่เคยมีคุณสมบัติที่แข็งแกร่งมาก แต่อย่างอื่นฉันเห็นด้วยอย่างเต็มที่ instanceofการรั่วไหลของรายละเอียดการใช้งานและแบ่งเป็นนามธรรม
Doval

17
"ไม่เคย" เป็นคำที่ฉันไม่ชอบในบริบทดังกล่าวโดยเฉพาะอย่างยิ่งเมื่อมันขัดแย้งกับสิ่งที่คุณเขียนด้านล่าง
Doc Brown

10
ถ้าโดย "ไม่เคย" คุณหมายถึง "บางครั้ง" จริง ๆ แล้วคุณพูดถูก
คาเลบ

2
ตกลงหมายถึงอนุญาตได้รับการยอมรับ ฯลฯ แต่ไม่จำเป็นต้องเหมาะหรือเหมาะสมที่สุด ความแตกต่างกับไม่ตกลง : หากสิ่งใดไม่ดีแล้วคุณไม่ควรทำเลย ในขณะที่คุณระบุจำเป็นที่จะต้องตรวจสอบประเภทของบางสิ่งบางอย่างที่สามารถบ่งบอกถึงปัญหาที่เกิดขึ้นลึกลงไปในรหัส แต่มีบางครั้งที่มันสมควรที่สุดตัวเลือกที่ดีอย่างน้อยและในสถานการณ์เช่นนี้ก็เห็นได้ชัดว่าตกลงที่จะใช้เครื่องมือที่ของคุณ การกำจัด (ถ้ามันเป็นเรื่องง่ายที่จะหลีกเลี่ยงพวกเขาในทุกสถานการณ์ที่พวกเขาอาจจะไม่ได้มีในสถานที่แรก.) คำถามเดือดลงระบุสถานการณ์เหล่านั้นและไม่เคยไม่ได้ช่วย
Caleb

2
@supercat: วิธีการควรมีความต้องการประเภทที่เฉพาะเจาะจงน้อยที่สุดที่ตอบสนองความต้องการของ IEnumerable<T>ไม่สัญญาว่าจะมีองค์ประกอบ "สุดท้าย" อยู่ หากวิธีการของคุณต้องการองค์ประกอบดังกล่าวก็ควรจะต้องมีประเภทที่รับประกันการมีอยู่ของหนึ่ง และชนิดย่อยของประเภทนั้นสามารถให้การใช้งานที่มีประสิทธิภาพของวิธี "ล่าสุด"
cHao

2

หากคุณมีฐานรหัสขนาดใหญ่ (มากกว่า 100K บรรทัดของรหัส) และอยู่ใกล้กับการจัดส่งหรือทำงานในสาขาที่จะต้องรวมในภายหลังดังนั้นจึงมีค่าใช้จ่าย / ความเสี่ยงจำนวนมากในการเปลี่ยนรับมือจำนวนมาก

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

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

หรือกล่าวอีกนัยหนึ่งเมื่อมีเป้าหมายที่จะจ่ายค่าจ้างของคุณแทนที่จะได้รับ“ คะแนนโหวต” สำหรับความสะอาดของการออกแบบของคุณ


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

คุณสามารถใช้“ การทดสอบการพิมพ์” เพื่อตัดสินใจว่าจะแสดง UI เวอร์ชันใดหรือมีตารางการค้นหาแฟนซีที่แปลงจาก“ โดเมนคลาส” เป็น“ คลาส UI” ตารางการค้นหาเป็นเพียงวิธีการซ่อน "การทดสอบประเภท" ในที่เดียว

(รหัสอัพเดทฐานข้อมูลอาจมีปัญหาเช่นเดียวกับรหัส UI แต่คุณมักจะมีรหัสอัปเดตฐานข้อมูลเพียงชุดเดียว แต่คุณสามารถมีหน้าจอที่แตกต่างกันจำนวนมากที่ต้องปรับให้เข้ากับประเภทของวัตถุที่แสดง)


รูปแบบผู้เยี่ยมชมมักเป็นวิธีที่ดีในการแก้ไขกรณี UI ของคุณ
เอียนโกลด์บาย

@IanGoldby เห็นด้วยในบางครั้งมันอาจเป็นไปได้ แต่คุณยังคงทำ "การทดสอบประเภท" ซ่อนอยู่เล็กน้อย
เอียน

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

@IanGoldby ฉันหมายถึงการซ่อนในแง่ที่ว่ามันสามารถทำให้รหัส WPF หรือ WinForms ยากต่อการเข้าใจ ฉันคาดหวังว่าสำหรับ UIs ฐานเว็บบางตัวมันจะทำงานได้ดีมาก
เอียน

2

การใช้งาน LINQ ใช้การตรวจสอบประเภทจำนวนมากสำหรับการปรับแต่งประสิทธิภาพที่เป็นไปได้จากนั้นเลือกทางเลือกสำหรับ IEnumerable

ตัวอย่างที่ชัดเจนที่สุดน่าจะเป็นวิธี ElementAt (ส่วนที่ตัดตอนมาเล็ก ๆ ของ. NET 4.5):

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) { 
    IList<TSource> list = source as IList<TSource>;

    if (list != null) return list[index];
    // ... and then an enumerator is created and MoveNext is called index times

แต่มีสถานที่มากมายในคลาส Enumerable ที่ใช้รูปแบบที่คล้ายกัน

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


มันอาจได้รับการออกแบบที่ดีขึ้นโดยการให้วิธีการที่สะดวกซึ่งอินเตอร์เฟสสามารถจัดหาการใช้งานเริ่มต้นจากนั้นมีIEnumerable<T>วิธีการมากมายเช่นเดียวกับในList<T>พร้อมกับFeaturesคุณสมบัติที่ระบุว่าวิธีการใดที่คาดว่าจะทำงานได้ดีช้าหรือไม่เลย รวมถึงข้อสมมติฐานต่าง ๆ ที่ผู้บริโภคสามารถทำได้เกี่ยวกับคอลเลกชันอย่างปลอดภัย (เช่นขนาดและ / หรือ เนื้อหาที่มีอยู่รับประกันว่าจะไม่เปลี่ยน [ประเภทอาจสนับสนุนAddในขณะที่ยังคงรับประกันว่าเนื้อหาที่มีอยู่จะไม่เปลี่ยนรูป])
supercat

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

1

มีตัวอย่างที่เกิดขึ้นบ่อยครั้งในการพัฒนาเกมโดยเฉพาะในการตรวจจับการชนซึ่งยากที่จะจัดการโดยไม่ต้องใช้การทดสอบประเภทบางประเภท

GameObjectสมมติว่าวัตถุเกมทั้งหมดมาจากระดับฐานร่วมกัน วัตถุแต่ละคนมีร่างกายรูปร่างชนแข็งCollisionShapeซึ่งอาจให้ติดต่อกัน (จะบอกว่าตำแหน่งแบบสอบถามรสนิยม ฯลฯ ) แต่รูปร่างการปะทะกันที่เกิดขึ้นจริงทั้งหมดจะ subclasses คอนกรีตเช่นSphere, Box, ConvexHullฯลฯ การจัดเก็บข้อมูลที่เฉพาะเจาะจงกับชนิดของวัตถุทางเรขาคณิตที่ (ดูตัวอย่างจริงที่นี่ )

ตอนนี้เพื่อทดสอบการชนฉันต้องเขียนฟังก์ชันสำหรับประเภทรูปร่างการชนกันแต่ละคู่:

detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...

ที่มีคณิตศาสตร์เฉพาะที่จำเป็นในการดำเนินการตัดกันของทั้งสองประเภทเรขาคณิต

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

ในทางปฏิบัติในสถานการณ์เช่นนี้เครื่องยนต์ฟิสิกส์ที่ฉันเคยเห็น (กระสุนและ Havok) พึ่งพาการทดสอบประเภทของรูปแบบเดียวหรืออื่น

ฉันไม่ได้บอกว่านี่เป็นวิธีแก้ปัญหาที่ดีแต่มันอาจเป็นวิธีที่ดีที่สุดในการแก้ไขปัญหานี้

* ในทางเทคนิคมันเป็นไปได้ที่จะใช้การจัดส่งแบบคู่ในวิธีที่น่ากลัวและซับซ้อนซึ่งจะต้องใช้ชุดค่าผสม N (N + 1) / 2 (โดยที่ N คือจำนวนชนิดรูปร่างที่คุณมี) และจะทำให้สับสนในสิ่งที่คุณทำ เป็นการหาประเภทของรูปร่างสองแบบพร้อมกันดังนั้นฉันจึงไม่คิดว่านี่จะเป็นทางออกที่สมจริง


1

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

ตัวอย่างเช่นคุณต้องการวาดเอนทิตีบางอย่าง แต่ไม่ต้องการเพิ่มรหัสการวาดลงในพวกเขาโดยตรง (ซึ่งสมเหตุสมผล) ในภาษาที่ไม่รองรับการส่งหลายครั้งคุณอาจท้ายด้วยรหัสต่อไปนี้:

void DrawEntity(Entity entity) {
    if (entity instanceof Circle) {
        DrawCircle((Circle) entity));
    else if (entity instanceof Rectangle) {
        DrawRectangle((Rectangle) entity));
    } ...
}

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


0

ครั้งเดียวที่ฉันใช้คือการรวมกับการสะท้อน แต่ถึงกระนั้นการตรวจสอบแบบไดนามิกส่วนใหญ่ไม่ได้เข้ารหัสยากไปยังคลาสที่เฉพาะเจาะจง (หรือเฉพาะฮาร์ดโค้ดไปยังคลาสพิเศษเช่นStringหรือList)

โดยการตรวจสอบแบบไดนามิกฉันหมายถึง:

boolean checkType(Type type, Object object) {
    if (object.isOfType(type)) {

    }
}

และไม่ได้เขียนโค้ดยาก

boolean checkIsManaer(Object object) {
    if (object instanceof Manager) {

    }
}

0

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

เมื่อคุณคิดถึงการออกแบบเชิงวัตถุในอุดมคติการทดสอบประเภท (และการคัดเลือกนักแสดง) ไม่ควรเกิดขึ้น แต่หวังว่าในตอนนี้คุณจะพบว่าการเขียนโปรแกรมเชิงวัตถุนั้นไม่เหมาะ บางครั้งโดยเฉพาะอย่างยิ่งเมื่อใช้รหัสระดับล่างรหัสอาจไม่เป็นจริงตามอุดมคติ นี่เป็นกรณีที่มี ArrayLists ใน Java เนื่องจากพวกเขาไม่ทราบว่ารันไทม์ว่าคลาสใดถูกเก็บไว้ในอาร์เรย์พวกมันสร้างObject[]อาร์เรย์และส่งไปยังประเภทที่ถูกต้องแบบคงที่

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

การทดสอบประเภทก็เกิดขึ้นบ่อยๆ บ่อยครั้งที่คุณจะมีวิธีการที่ส่งคืนObject[]หรืออาร์เรย์ทั่วไปอื่น ๆ และคุณต้องการดึงFooวัตถุทั้งหมดออกด้วยเหตุผลใดก็ตาม นี่คือการใช้การทดสอบและการคัดเลือกนักแสดงที่ถูกต้องตามกฎหมายอย่างสมบูรณ์แบบ

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


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

1
ในทางเทคนิคคอลเลกชัน Java ไม่ได้โยนเนื้อหาของพวกเขาเพื่ออะไร คอมไพเลอร์ฉีด typecasts ที่ตำแหน่งของการเข้าถึงแต่ละครั้ง:String x = (String) myListOfStrings.get(0)

ครั้งล่าสุดที่ฉันดูซอร์สโค้ด Java (ซึ่งอาจเป็น 1.6 หรือ 1.5) มีเพี้ยนอย่างชัดเจนในแหล่ง ArrayList มันสร้างคำเตือนคอมไพเลอร์ (ระงับ) สำหรับเหตุผลที่ดี แต่ได้รับอนุญาตต่อไป ฉันคิดว่าคุณสามารถพูดได้ว่าเพราะวิธีการใช้งาน generics มันเป็นเสมอObjectจนกว่าจะมีการเข้าถึงต่อไป; generics ใน Java ให้เฉพาะการส่อให้เห็นโดยนัยทำให้ปลอดภัยโดยกฎคอมไพเลอร์
meustrus

0

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

abstract class Vehicle
{
    abstract void Process();
}

class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }

สมมติว่าตรรกะทางธุรกิจของเรานั้นแท้จริงแล้วว่า "รถยนต์ทุกคันมีความสำคัญเหนือกว่าเรือและรถบรรทุก" การเพิ่มPriorityพร็อพเพอร์ตี้ในคลาสไม่อนุญาตให้คุณแสดงตรรกะทางธุรกิจนี้อย่างหมดจดเพราะคุณจะต้องจบเรื่องนี้:

abstract class Vehicle
{
    abstract void Process();
    abstract int Priority { get }
}

class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }

ปัญหาคือตอนนี้เข้าใจลำดับความสำคัญที่คุณต้องดู subclasses ทั้งหมดหรือในคำอื่น ๆ ที่คุณได้เพิ่มการมีเพศสัมพันธ์กับ subclasses

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

static class Priorities
{
    public const int CAR_PRIORITY = 1;
    public const int BOAT_PRIORITY = 2;
    public const int TRUCK_PRIORITY = 2;
}

อย่างไรก็ตามในความเป็นจริงขั้นตอนวิธีการตั้งเวลาเป็นสิ่งที่อาจมีการเปลี่ยนแปลงในอนาคตและในที่สุดมันก็อาจขึ้นอยู่กับมากกว่าเพียงแค่พิมพ์ ตัวอย่างเช่นอาจพูดว่า "รถบรรทุกที่มีน้ำหนักเกิน 5,000 กิโลกรัมได้รับการจัดลำดับความสำคัญเป็นพิเศษสำหรับรถคันอื่น ๆ " นั่นเป็นเหตุผลที่อัลกอริทึมการจัดตารางเวลาอยู่ในชั้นเรียนของตนเองและเป็นความคิดที่ดีที่จะตรวจสอบประเภทเพื่อกำหนดว่าควรจะใช้อันไหนก่อน:

class VehicleScheduler : IScheduleVehicles
{
    public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
    {
        if(vehicle1 is Car) return vehicle1;
        if(vehicle2 is Car) return vehicle2;
        return vehicle1;
    }
}

นี่เป็นวิธีที่ตรงไปตรงมาที่สุดในการใช้ตรรกะทางธุรกิจและยังคงมีความยืดหยุ่นมากที่สุดต่อการเปลี่ยนแปลงในอนาคต


ฉันไม่เห็นด้วยกับตัวอย่างของคุณ แต่จะเห็นด้วยกับหลักการของการอ้างอิงส่วนตัวที่อาจมีประเภทที่แตกต่างกันที่มีความหมายแตกต่างกัน สิ่งที่ฉันแนะนำเป็นตัวอย่างที่ดีกว่าก็คือฟิลด์ที่สามารถเก็บnulla Stringหรือ a String[]ได้ หาก 99% ของวัตถุจะต้องมีหนึ่งสายการห่อหุ้มทุกสายในการสร้างแยกString[]อาจเพิ่มค่าใช้จ่ายในการจัดเก็บจำนวนมาก การจัดการตัวพิมพ์เล็ก - สายเดี่ยวโดยใช้การอ้างอิงโดยตรงกับ a Stringจะต้องใช้รหัสเพิ่มเติม แต่จะบันทึกที่เก็บข้อมูลและอาจทำให้สิ่งต่าง ๆ เร็วขึ้น
supercat

0

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

ในซอฟต์แวร์ของเราเราได้รับข้อความผ่านเครือข่ายเพื่อตอบสนองต่อคำขอ ข้อความที่มีการดีซีเรียลMessageไลซ์ทั้งหมดแชร์คลาสพื้นฐานทั่วไป

คลาสเองนั้นง่ายมากเพียงแค่เพย์โหลดเป็นคุณสมบัติ C # ที่พิมพ์และรูทีนสำหรับ marshalling และ unmarshalling (อันที่จริงฉันสร้างคลาสส่วนใหญ่โดยใช้เทมเพลต t4 จากคำอธิบาย XML ของรูปแบบข้อความ)

รหัสจะเป็นสิ่งที่ชอบ:

Message response = await PerformSomeRequest(requestParameter);

// Server (out of our control) would send one message as response, but 
// the actual message type is not known ahead of time (it depended on 
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{ 
    // Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
    // Extract results
}
else if (response is AnotherThingHappenedMessage)
{
    // Extract another type of result
}
// Half a dozen other possible checks for messages

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

น่าสังเกตว่า C # 7.0 ได้รับการจับคู่รูปแบบ (ซึ่งในหลาย ๆ ด้านคือการทดสอบประเภทบนเตียรอยด์) มันไม่เลวเลย ...


0

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


ดี deserializers JSON บางส่วนจะพยายามสร้างอินสแตนซ์ทรีวัตถุถ้าโครงสร้างของคุณมีข้อมูลประเภทสำหรับ "เขตข้อมูลการอนุมาน" ในนั้น แต่ใช่ ฉันคิดว่านี่เป็นทิศทางที่คำตอบของ Karl B มุ่งหน้าไป
svidgen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.