อะไรคือสาเหตุของการใช้อินเตอร์เฟสกับชนิดที่มีข้อ จำกัด โดยทั่วไป


15

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

//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}

อะไรคือเหตุผลที่ใช้ประเภทอินเตอร์เฟสจริงมากกว่าชนิดที่ จำกัด โดยอินเตอร์เฟสเหล่านั้น ตัวอย่างเช่นอะไรคือเหตุผลในการสร้างวิธีการเซ็นชื่อI2 ExampleMethod(I2 value)?


4
แม่แบบคลาส (C ++) เป็นสิ่งที่แตกต่างอย่างสิ้นเชิงและมีประสิทธิภาพมากกว่า generics ที่เลวทรามต่ำช้า แม้ว่าภาษาที่มี generics ยืมไวยากรณ์แม่แบบสำหรับพวกเขา
Deduplicator

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

@Deduplicator: เมื่อพิจารณาว่า generics นั้นแก่กว่าเทมเพลตฉันไม่สามารถดูว่า generics สามารถยืมอะไรจากเทมเพลตเลยไวยากรณ์หรืออย่างอื่น
Jörg W Mittag

3
@ JörgWMittag: ฉันสงสัยว่า "ภาษาเชิงวัตถุที่สนับสนุนชื่อสามัญ" Deduplicator อาจเข้าใจ "Java และ C #" มากกว่า "ML และ Ada" จากนั้นอิทธิพลของ C ++ ในอดีตก็ชัดเจนแม้ว่าจะไม่ใช่ภาษาทุกภาษาที่มีชื่อสามัญหรือตัวแปรหลากหลายที่ยืมมาจาก C ++
Steve Jessop

2
@SteveJessop: ML, Ada, Eiffel, Haskell ลงวันที่ก่อนเทมเพลต C ++, Scala, F #, OCaml มาแล้วและไม่มีพวกเขาแบ่งปันไวยากรณ์ของ C ++ (ที่น่าสนใจแม้กระทั่ง D ซึ่งยืมมาจาก C ++ โดยเฉพาะเทมเพลตจะไม่แชร์ไวยากรณ์ของ C ++) "Java และ C #" เป็นมุมมองที่ค่อนข้างแคบของ "ภาษาที่มีชื่อสามัญ" ฉันคิดว่า
Jörg W Mittag

คำตอบ:


21

ใช้รุ่นพารามิเตอร์ให้

  1. ข้อมูลเพิ่มเติมให้กับผู้ใช้ฟังก์ชั่น
  2. จำกัด จำนวนของโปรแกรมที่คุณสามารถเขียนได้ (ตรวจสอบบั๊กฟรี)

เป็นตัวอย่างแบบสุ่มสมมติว่าเรามีวิธีที่คำนวณรากของสมการกำลังสอง

int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}

แล้วคุณต้องการให้มันทำงานกับจำนวนอื่น ๆ ที่คล้าย ๆintกัน คุณสามารถเขียนสิ่งที่ชอบ

Num solve(Num a, Num b, Num c){
  ...
}

ปัญหาคือสิ่งนี้ไม่ได้บอกสิ่งที่คุณต้องการ มันบอกว่า

ให้ 3 สิ่งที่มีจำนวนเหมือนกัน (ไม่จำเป็นต้องเหมือนกัน) และฉันจะให้คุณกลับจำนวน

เราไม่สามารถทำอะไรเช่นint sol = solve(a, b, c)ถ้าa, bและcเป็นintเพราะเราไม่ทราบว่าวิธีการที่จะไปกลับintในที่สุด! สิ่งนี้นำไปสู่การเต้นที่น่าอึดอัดใจบางอย่างกับการทำให้เศร้าใจและอธิษฐานหากเราต้องการใช้วิธีแก้ปัญหาในการแสดงออกที่ใหญ่ขึ้น

ภายในฟังก์ชั่นบางคนอาจมอบทุ่นให้เราเป็นใหญ่และองศาและเราจะต้องเพิ่มและคูณพวกเขาเข้าด้วยกัน เราต้องการที่จะปฏิเสธสิ่งนี้เนื่องจากการดำเนินการระหว่าง 3 คลาสนี้จะเป็นการพูดพล่อยๆ องศาคือ mod 360 ดังนั้นมันจะไม่เป็นอย่างนั้นa.plus(b) = b.plus(a)และจะมีความฮือฮาที่คล้ายกันเกิดขึ้น

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

<T : Num> T solve(T a, T b, T c)

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

เรื่องนี้เกิดขึ้นในหลาย ๆ ที่เช่นกัน แหล่งที่ดีอีกประการหนึ่งของตัวอย่างฟังก์ชั่นที่เป็นนามธรรมมากกว่าการจัดเรียงของตู้คอนเทนเนอร์, Ala บางreverse, sort, mapฯลฯ


8
โดยสรุปรุ่นทั่วไปรับประกันว่าทั้งสามอินพุต (และเอาต์พุต) จะเป็นหมายเลขประเภทเดียวกัน
MathematicalOrchid

อย่างไรก็ตามสิ่งนี้จะสั้นเมื่อคุณไม่ได้ควบคุมประเภทที่เป็นปัญหา (และไม่สามารถเพิ่มอินเทอร์เฟซให้) เพื่อความทั่วๆไปสูงสุดคุณจะต้องยอมรับส่วนต่อประสานที่กำหนดโดยประเภทอาร์กิวเมนต์ (เช่นNum<int>) เป็นอาร์กิวเมนต์พิเศษ คุณสามารถนำอินเตอร์เฟสไปใช้กับประเภทใดก็ได้ผ่านการมอบหมาย นี่คือสิ่งที่คลาสประเภทของ Haskell ยกเว้นน่าเบื่อกว่าการใช้เนื่องจากคุณต้องส่งผ่านอินเตอร์เฟสอย่างชัดเจน
Doval

16

อะไรคือเหตุผลที่ใช้ประเภทอินเตอร์เฟสจริงมากกว่าประเภทที่ จำกัด โดยอินเตอร์เฟสเหล่านั้น

เพราะนั่นคือสิ่งที่คุณต้องการ ...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

มีสองลายเซ็นที่แตกต่างกันอย่างเด็ดขาด ครั้งแรกที่ใช้ชนิดใด ๆ ที่ใช้อินเทอร์เฟซและสิ่งเดียวที่รับประกันได้ก็คือค่าส่งคืนเป็นไปตามอินเตอร์เฟส

อย่างที่สองใช้ชนิดใด ๆ ที่นำอินเตอร์เฟสมาใช้และรับรองว่าจะกลับมาอย่างน้อยชนิดนั้นอีกครั้ง

บางครั้งคุณต้องการรับประกันที่อ่อนแอกว่า บางครั้งคุณต้องการที่แข็งแกร่ง


คุณสามารถยกตัวอย่างวิธีใช้เวอร์ชันการรับประกันที่อ่อนแอกว่าได้หรือไม่?
GregRos

4
@GregRos - ตัวอย่างเช่นในรหัส parser บางฉันเขียน ฉันมีฟังก์ชั่นOrที่รับParserวัตถุสองชิ้น (คลาสฐานนามธรรม แต่หลักการมีอยู่) และส่งกลับค่าใหม่Parser(แต่มีประเภทที่แตกต่างกัน) ผู้ใช้ไม่ควรทราบหรือไม่สนใจว่าคอนกรีตเป็นประเภทใด
Telastyn

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

1
@NtscCobalt: มีประโยชน์มากกว่าเมื่อคุณรวมทั้งการเขียนโปรแกรมแบบพารามิเตอร์และอินเทอร์เฟซแบบอินเทอร์เฟซ เช่น LINQ ทำอะไรตลอดเวลา (ยอมรับตอบIEnumerable<T>กลับIEnumerable<T>ซึ่งก็คืออันที่จริงแล้วOrderedEnumerable<T>)
Ben Voigt

2

การใช้ generics ที่มีข้อ จำกัด สำหรับพารามิเตอร์ method สามารถอนุญาตให้เมธอดกลับมามากตามชนิดของสิ่งที่ส่งผ่านใน. NET พวกเขาสามารถมีข้อดีเพิ่มเติมเช่นกัน ในหมู่พวกเขา:

  1. วิธีการที่ยอมรับข้อ จำกัด ทั่วไปในฐานะที่เป็นrefหรือoutพารามิเตอร์อาจถูกส่งผ่านตัวแปรซึ่งเป็นไปตามข้อ จำกัด ; ในทางตรงกันข้ามวิธีการที่ไม่ใช่แบบทั่วไปที่มีพารามิเตอร์ประเภทอินเตอร์เฟสจะถูก จำกัด ให้ยอมรับตัวแปรของประเภทอินเตอร์เฟสที่แน่นอน

  2. วิธีการที่มีพารามิเตอร์ประเภททั่วไป T สามารถยอมรับคอลเลกชันทั่วไปของ T วิธีที่ยอมรับIList<T> where T:IAnimalจะสามารถที่จะยอมรับList<SiameseCat>แต่วิธีการที่ต้องการIList<Animal>จะไม่สามารถทำได้

  3. ข้อ จำกัด where T:IComparable<T>บางครั้งสามารถระบุอินเตอร์เฟซในแง่ของประเภททั่วไปเช่น

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

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

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

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