ทำไมคอลเลกชัน Java จึงถูกนำไปใช้กับ“ วิธีการเสริม” ในอินเตอร์เฟส


69

ในระหว่างการนำไปใช้ครั้งแรกของฉันการขยายเฟรมเวิร์กคอลเลกชัน Java ฉันค่อนข้างประหลาดใจที่เห็นว่าอินเตอร์เฟสการรวบรวมมีวิธีการประกาศเป็นทางเลือก ผู้ดำเนินการคาดว่าจะโยน UnsupportedOperationExceptions หากไม่ได้รับการสนับสนุน สิ่งนี้ทำให้ฉันกลายเป็นตัวเลือกการออกแบบ API ที่แย่ทันที

หลังจากอ่านหนังสือ "Effective Java" ที่ยอดเยี่ยมของ Joshua Bloch แล้วหลังจากเรียนรู้ว่าเขาอาจต้องรับผิดชอบต่อการตัดสินใจเหล่านี้ ฉันคิดว่าการประกาศอินเทอร์เฟซสองรายการ: คอลเลกชันและ MutableCollection ซึ่งขยายคอลเลกชันด้วยวิธีการ "ทางเลือก" จะทำให้รหัสลูกค้าที่บำรุงรักษาได้มากขึ้น

มีบทสรุปที่ดีของปัญหาที่เป็นที่นี่

มีเหตุผลที่ดีหรือไม่ที่เลือกวิธีเสริมแทนที่จะใช้สองอินเตอร์เฟส


IMO ไม่มีเหตุผลที่ดี C ++ STL ถูกสร้างโดย Stepanov ในช่วงต้นยุค 80 แม้ว่าความสามารถในการใช้งานของมันจะถูก จำกัด ด้วยไวยากรณ์เทมเพลต C ++ ที่น่าอึดอัดใจ แต่มันก็เป็นส่วนหนึ่งของความสอดคล้องและการใช้งานเมื่อเทียบกับคลาสคอลเลกชัน Java
kevin cline

มี C ++ STL 'porting' ไปยัง Java แต่มันใหญ่กว่า 5 เท่าในการนับชั้นเรียน ด้วยความคิดนี้ฉันไม่ซื้อเลยว่ายิ่งใหญ่กว่ามากเท่าไหร่ก็ใช้งานได้ดีขึ้น docs.oracle.com/javase/6/docs/technotes/guides/collections/…
m3th0dman

@ m3th0dman มีขนาดใหญ่กว่าใน Java เพราะ Java ไม่มีอะไรเทียบเท่ากับเทมเพลต C ++
วินไคลน์

บางทีมันอาจจะเป็นเพียงบางรูปแบบที่แปลกผมได้พัฒนา แต่รหัสของฉันมีแนวโน้มที่จะรักษาคอลเลกชันทั้งหมดเป็น "อ่านเท่านั้น" (อย่างแม่นยำมากขึ้นเพียงแค่สิ่งที่คุณนับหรือมากกว่าที่คุณย้ำ) ยกเว้นสำหรับไม่กี่วิธีที่จริงการสร้างคอลเลกชัน อาจเป็นวิธีปฏิบัติที่ดีอยู่ดี (โดยเฉพาะสำหรับการทำงานพร้อมกัน) และวิธีการทางเลือกที่หลายคนบ่นไม่เคยเป็นเรื่องจริงสำหรับฉัน หรือฝันร้ายของ Generics ที่มี "ยอดเยี่ยม" และ "ขยาย" ที่เคยมี (มาก) ของปัญหา แค่สงสัยว่าคนอื่นใช้แบบฝึกหัดนี้หรือไม่?
949300

คำตอบ:


28

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


6
ทั้งหมดนี้จะหลีกเลี่ยงได้หาก Java มีconstคำหลักเช่น C ++
Etienne de Martel

@Etienne: ดีกว่าจะเป็น metaclasses เหมือน Python จากนั้นคุณสามารถสร้างส่วนต่อประสานแบบเป็นโปรแกรมได้ ปัญหาของ const คือมันให้คุณหนึ่งหรือสองมิติvector<int>, const vector<int>, vector<const int>, const vector<const int>เท่านั้น: . จนถึงดีมาก แต่จากนั้นคุณลองใช้กราฟและคุณต้องการทำให้โครงสร้างกราฟคงที่ แต่แอตทริบิวต์ของโหนดสามารถแก้ไขได้ ฯลฯ
Neil G

2
@Enene ยังมีอีกเหตุผลที่ทำให้ "Learn Scala" ในรายการที่ต้องทำ!
glenviewjeff

2
สิ่งที่ฉันไม่เข้าใจคือทำไมพวกเขาไม่ทำcanวิธีที่จะทดสอบว่าการดำเนินการเป็นไปได้หรือไม่ มันจะทำให้ส่วนต่อประสานง่ายและรวดเร็ว
Mehrdad

3
@Etienne de Martel Nonsense นั่นจะช่วยได้อย่างไรในการระเบิดแบบ combinatorial
Tom Hawtin - tackline

10

ฟังดูแล้วเหมือนกับว่าInterface Segregation Principleยังไม่ได้มีการสำรวจย้อนกลับไปเหมือนตอนนี้ วิธีการทำสิ่งต่าง ๆ (เช่นอินเทอร์เฟซของคุณรวมถึงการดำเนินการทั้งหมดที่เป็นไปได้และคุณมีวิธี "เสื่อม" ที่ส่งข้อยกเว้นสำหรับสิ่งที่คุณไม่ต้องการ) ได้รับความนิยมก่อน SOLID และ ISP กลายเป็นมาตรฐานมาตรฐานสำหรับรหัสคุณภาพ


2
ทำไมต้องลงคะแนน มีคนไม่ชอบ ISP ใช่ไหม
Wayne Molina

นอกจากนี้ยังเป็นที่น่าสังเกตว่าในกรอบที่สนับสนุนความแปรปรวนจะมีข้อได้เปรียบอย่างมากในการแยกแง่มุมเหล่านั้นของอินเทอร์เฟซที่สามารถแปรปรวนหรือแปรปรวนจากสิ่งที่ไม่เปลี่ยนแปลง แม้ขาดการสนับสนุนดังกล่าวในกรอบที่ไม่ใช้การลบประเภทจะมีค่าในการแยกแง่มุมของอินเทอร์เฟซที่เป็นประเภทอิสระ (เช่นการอนุญาตให้มีหนึ่งCountคอลเลกชันโดยไม่ต้องกังวลว่า แต่ในเฟรมเวิร์กประเภทการลบข้อมูลเช่น Java ที่ไม่ใช่ปัญหาดังกล่าว
supercat

4

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

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

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

โปรดทราบว่าการดำรงอยู่ของ "ความสามารถเสริม" ในบางกรณีอาจอนุญาตให้มีการรวบรวมคอลเลกชันเพื่อใช้งานบางอย่างในรูปแบบที่มีประสิทธิภาพมากกว่าที่จะเป็นไปได้หากความสามารถนั้นถูกกำหนดโดยการใช้งาน ตัวอย่างเช่นสมมติว่ามีconcatenateการใช้วิธีการในการรวบรวมคอมโพสิตจากสองวิธีแรกเกิดขึ้นเป็น ArrayList ที่มี 1,000,000 องค์ประกอบและสุดท้ายคือการรวบรวมยี่สิบองค์ประกอบซึ่งสามารถทำซ้ำได้ตั้งแต่เริ่มต้นเท่านั้น หากการรวบรวมคอมโพสิตถูกถามสำหรับองค์ประกอบ 1,000,013 (ดัชนี 1,000,012) ก็สามารถถาม ArrayList ว่ามีรายการจำนวนเท่าใด (เช่น 1,000,000) ลบออกจากดัชนีที่ร้องขอ (ให้ผล 12) อ่านและข้ามองค์ประกอบสิบสองจากวินาที แล้วกลับองค์ประกอบต่อไป

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


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

1
ขออภัยความคิดเห็นที่ไม่สมบูรณ์ ฉันกำลังจะบอกว่า "วิธีการถามเกี่ยวกับการสะสม '... " ย้ายสิ่งที่ควรจะตรวจสอบเวลารวบรวมเพื่อรันไทม์
kevin cline

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

... แต่จะได้รับประโยชน์จากคุณสมบัติของมันมีอยู่จะดีกว่าถ้ามีรหัสถามว่าวัตถุนั้นสนับสนุนคุณสมบัติหรือไม่มากกว่าถามระบบประเภทว่าประเภทของวัตถุนั้นสัญญาว่าจะสนับสนุนคุณสมบัติหรือไม่ หากอินเทอร์เฟซ "เฉพาะ" จะถูกใช้อย่างกว้างขวางหนึ่งอาจรวมถึงAsXXXวิธีการในอินเทอร์เฟซพื้นฐานซึ่งจะส่งคืนวัตถุตามที่มันถูกเรียกถ้ามันใช้อินเทอร์เฟซที่ส่งคืนวัตถุ wrapper ที่สนับสนุนอินเทอร์เฟซนั้นถ้าเป็นไปได้ ข้อยกเว้นถ้าไม่ ตัวอย่างเช่นImmutableCollectionอินเทอร์เฟซอาจต้องการโดยสัญญา ...
supercat

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

-1

ฉันจะให้ความสำคัญกับนักพัฒนาดั้งเดิมโดยที่ไม่รู้ดีกว่า เรามาไกลในการออกแบบ OO ตั้งแต่ปี 1998 หรือมากกว่านั้นเมื่อ Java 2 และ Collections เปิดตัวครั้งแรก สิ่งที่ดูเหมือนว่าการออกแบบที่ไม่ดีอย่างเห็นได้ชัดตอนนี้ไม่ชัดเจนในช่วงแรก ๆ ของ OOP

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

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


7
หล่ออะไร หากวิธีการส่งกลับคุณCollectionและไม่ใช่ a MutableCollectionมันเป็นสัญญาณที่ชัดเจนว่ามันไม่ได้หมายถึงการได้รับการแก้ไข ฉันไม่รู้ว่าทำไมต้องมีใครหล่อ การมีอินเทอร์เฟซแยกต่างหากหมายความว่าคุณจะได้รับข้อผิดพลาดประเภทนั้นในเวลารวบรวมแทนที่จะได้รับการยกเว้นในเวลาทำงาน ยิ่งคุณได้รับข้อผิดพลาดเร็วขึ้นเท่าไหร่
Etienne de Martel

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

6
ถ้าฉันให้คอลเลกชันแบบอ่านอย่างเดียวนั่นเป็นเพราะฉันไม่ต้องการให้แก้ไข การโยนข้อยกเว้นและอาศัยเอกสารเป็นความรู้สึกที่แย่มากเหมือนปะ ใน C ++ ฉันแค่คืนค่าconstวัตถุบอกผู้ใช้ทันทีว่าไม่สามารถแก้ไขวัตถุได้
Etienne de Martel

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