ส่งคืน ImmutableMap หรือ Map ดีกว่าไหม


90

สมมติว่าผมเขียนวิธีการที่ควรจะกลับเป็นแผนที่ ตัวอย่างเช่น:

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

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

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

ฉันควรปล่อยประเภทการส่งคืนเป็นแผนที่ทั่วไปหรือฉันควรระบุว่าฉันส่งคืน ImmutableMap?

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


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

จะเกิดอะไรขึ้นถ้าในภายหลังคุณตัดสินใจว่าควรส่งคืนแผนที่ที่ไม่แน่นอน?
user253751

3
@immibis lol หรือรายการสำหรับเรื่องนั้น?
djechlin

3
คุณต้องการอบการพึ่งพา Guava ใน API ของคุณหรือไม่? คุณจะไม่สามารถเปลี่ยนแปลงได้โดยไม่ทำลายความเข้ากันได้แบบย้อนหลัง
user2357112 รองรับ Monica

1
ฉันคิดว่าคำถามนั้นไม่ชัดเจนเกินไปและไม่มีรายละเอียดที่สำคัญหลายอย่าง ตัวอย่างเช่นคุณมี Guava เป็นที่พึ่ง (มองเห็นได้) อยู่แล้วหรือไม่ แม้ว่าคุณจะมีอยู่แล้ว แต่ข้อมูลโค้ดก็คือ pesudocode และไม่ได้สื่อถึงสิ่งที่คุณต้องการบรรลุ คุณจะแน่นอนไม่ได้return ImmutableMap.of(somethingElse)เขียน แต่คุณจะจัดเก็บImmutableMap.of(somethingElse)เป็นฟิลด์และส่งคืนเฉพาะฟิลด์นี้ ทั้งหมดนี้มีอิทธิพลต่อการออกแบบที่นี่
Marco13

คำตอบ:


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

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

  • ถ้าคุณเขียนเครื่องมือภายในห้องสมุด / Mapมันจะกลายเป็นที่ยอมรับมากขึ้นเพียงแค่กลับธรรมดา ผู้คนจะรู้เกี่ยวกับภายในของรหัสที่พวกเขาเรียกหรืออย่างน้อยก็จะสามารถเข้าถึงได้ง่าย

ข้อสรุปของฉันคือชัดเจนดีอย่าปล่อยให้โอกาส


1
อินเทอร์เฟซพิเศษหนึ่งคลาสนามธรรมพิเศษหนึ่งคลาสและอีกหนึ่งคลาสที่ขยายคลาสนามธรรมนี้ นี่เป็นเรื่องยุ่งยากมากเกินไปสำหรับเรื่องง่ายๆเช่นที่ OP กำลังถามซึ่งก็คือถ้าวิธีการส่งคืนMapประเภทอินเทอร์เฟซหรือImmutableMapประเภทการใช้งาน
fps

20
หากคุณกำลังอ้างถึง Guava ImmutableMapคำแนะนำของทีม Guava คือการพิจารณาImmutableMapอินเทอร์เฟซที่มีการรับประกันความหมายและคุณควรส่งคืนประเภทนั้นโดยตรง
Louis Wasserman

1
@LouisWasserman มมไม่เห็นว่าคำถามกำลังให้ลิงค์ไปยังการใช้งาน Guava ฉันจะลบข้อมูลโค้ดออกแล้วขอบคุณ
Dici

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

1
@WSimpson ฉันเชื่อว่านั่นคือคำตอบของฉัน Louis กำลังพูดถึงตัวอย่างข้อมูลบางส่วนที่ฉันเพิ่ม แต่ฉันลบออก
Dici

38

คุณควรมีImmutableMapเป็นประเภทผลตอบแทนของคุณ Mapมีวิธีการที่จะไม่ได้รับการสนับสนุนโดยการดำเนินการImmutableMap(เช่นput) และมีการทำเครื่องหมายใน@deprecatedImmutableMap

การใช้เมธอดที่เลิกใช้แล้วจะส่งผลให้คอมไพเลอร์เตือน & IDE ส่วนใหญ่จะเตือนเมื่อมีคนพยายามใช้เมธอดที่เลิกใช้

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


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

@TonioElGringo Collections.immutableMapแล้วไง?
OrangeDog

@TonioElGringo โปรดทราบว่าวิธีการที่ ImmutableMap ไม่ใช้มีการทำเครื่องหมายอย่างชัดเจนเป็นตัวเลือกในอินเตอร์เฟซของเอกสารdocs.oracle.com/javase/7/docs/api/java/util/... ดังนั้นฉันคิดว่ามันยังสมเหตุสมผลที่จะคืนแผนที่ (ด้วย Javadocs ที่เหมาะสม) แม้ว่าฉันเลือกที่จะส่งคืน ImmutableMap อย่างชัดเจนเนื่องจากเหตุผลที่กล่าวไว้ข้างต้น
AMT

3
@TonioElGringo ไม่ได้ละเมิดอะไรวิธีการเหล่านั้นเป็นทางเลือก เช่นเดียวกับในListไม่มีการรับประกันว่าList::addถูกต้อง ลองArrays.asList("a", "b").add("c");. ไม่มีข้อบ่งชี้ว่าจะล้มเหลวในขณะรันไทม์ แต่ก็เป็นเช่นนั้น อย่างน้อยฝรั่งก็ให้หัว
njzk2

14

ในทางกลับกันถ้าฉันปล่อยไว้แบบนี้นักพัฒนารายอื่นอาจพลาดข้อเท็จจริงที่ว่าวัตถุนี้ไม่เปลี่ยนรูป

คุณควรพูดถึงสิ่งนั้นใน Javadocs นักพัฒนาอ่านพวกเขาคุณก็รู้

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

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

โปรดทราบว่ามีเพียงMapตัวมันเองเท่านั้นที่จะไม่เปลี่ยนรูปไม่ใช่วัตถุที่มีอยู่


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

1
โอเคทอมพูดถูกฉันไม่แน่ใจว่าคุณพยายามจะพูดอะไร แต่สิ่งที่คุณเขียนนั้นเป็นเท็จอย่างชัดเจน (นอกจากนี้: เขยิบเพื่อการรวมเพศ)
djechlin

@ ทอมฉันเดาว่าคุณพลาดคำถากถางในคำตอบนี้
Alma Alma

10

ถ้าฉันปล่อยไว้แบบนี้นักพัฒนาคนอื่นอาจพลาดข้อเท็จจริงที่ว่าวัตถุนี้ไม่เปลี่ยนรูป

นั่นเป็นเรื่องจริง แต่นักพัฒนาซอฟต์แวร์รายอื่นควรทดสอบโค้ดของตนและตรวจสอบให้แน่ใจว่าครอบคลุม

อย่างไรก็ตามคุณมีอีก 2 ทางเลือกในการแก้ปัญหานี้:

  • ใช้ Javadoc

    @return a immutable map
    
  • เลือกชื่อวิธีการอธิบาย

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    สำหรับกรณีการใช้งานที่เป็นรูปธรรมคุณสามารถตั้งชื่อวิธีการให้ดีขึ้นได้ เช่น

    public Map<String, Integer> getUnmodifiableCountByWords()
    

คุณทำอะไรได้อีก?!

คุณสามารถส่งคืนไฟล์

  • สำเนา

    private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    ควรใช้แนวทางนี้หากคุณคาดว่าจะมีลูกค้าจำนวนมากแก้ไขแผนที่และตราบใดที่แผนที่มีเพียงไม่กี่รายการ

  • CopyOnWriteMap

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

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

    ควรใช้แนวทางนี้หากคุณคาดว่าลูกค้าบางรายจะแก้ไขแผนที่

    น่าเศร้าที่ java ไม่มีไฟล์CopyOnWriteMap. แต่คุณอาจพบบุคคลที่สามหรือดำเนินการด้วยตัวเอง

ในที่สุดคุณควรจำไว้ว่าองค์ประกอบในแผนที่ของคุณอาจยังคงไม่แน่นอน


8

ส่งคืน ImmutableMap อย่างแน่นอนเหตุผลคือ:

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

5

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

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

ฝรั่งจะลอกแผนที่ทุกครั้ง ช้าจัง แต่ถ้าinternalMapเป็นแล้วImmutableMapก็ไม่เป็นไร

หากคุณไม่ จำกัด ชั้นเรียนของคุณในการกลับมาImmutableMapคุณสามารถกลับมาได้Collections.unmodifiableMapเช่นนั้น:

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

โปรดทราบว่านี่เป็นมุมมองที่ไม่เปลี่ยนรูปในแผนที่ หากการเปลี่ยนแปลงนั้นจะสำเนาแคชของinternalMap Collections.unmodifiableMap(internalMap)อย่างไรก็ตามฉันยังคงชอบมันสำหรับ getters


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

@As davidback Collections.unmodifiableMapแสดงความคิดเห็นให้ระมัดระวังเกี่ยวกับการใช้ วิธีนี้จะส่งคืนมุมมองไปยังแผนที่ดั้งเดิมแทนที่จะสร้างวัตถุแผนที่ใหม่ที่แตกต่างกัน ดังนั้นรหัสอื่น ๆ ที่อ้างถึงแผนที่ดั้งเดิมสามารถแก้ไขได้จากนั้นผู้ถือแผนที่ที่ไม่สามารถปรับเปลี่ยนได้จะเรียนรู้วิธีที่ยากที่แผนที่จะสามารถแก้ไขได้จากภายใต้แผนที่นั้น ฉันได้รับความจริงนั้นเล็กน้อย ImmutableMapผมได้เรียนรู้ไปทั้งกลับสำเนาของแผนที่หรือใช้ฝรั่ง
Basil Bourque

5

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

public Integer fooOf(String key) {
    return map.get(key);
}

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

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

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

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}

-1

แผนที่ไม่เปลี่ยนรูปเป็นแผนที่ประเภทหนึ่ง ดังนั้นการออกจากประเภทการส่งคืนแผนที่ก็ไม่เป็นไร

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


-1

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

ดูบทความต่อไปนี้:

แอนดี้กิบสัน


1
เครื่องห่อที่ไม่จำเป็นถือว่าเป็นอันตราย
djechlin

คุณพูดถูกฉันจะไม่ใช้ Wrapper ที่นี่เช่นกันเนื่องจากอินเทอร์เฟซเพียงพอ
WSimpson

ฉันรู้สึกว่านี่เป็นสิ่งที่ถูกต้องที่จะทำแล้ว ImmutableMap จะใช้ ImmutableMapInterface อยู่แล้ว แต่ฉันไม่เข้าใจในทางเทคนิค
djechlin

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

ข้อเสียของการส่งคืนสิ่งที่ไม่ขยาย (หรือใช้งาน) Mapอินเทอร์เฟซคือคุณไม่สามารถส่งต่อไปยังฟังก์ชัน API ใด ๆ ที่คาดหวังMapโดยไม่ต้องแคสต์ ซึ่งรวมถึงผู้สร้างสำเนาแผนที่ประเภทอื่น ๆ ทุกประเภท นอกจากนั้นหากอินเทอร์เฟซมีการ "ซ่อน" เท่านั้นผู้ใช้ของคุณสามารถแก้ไขปัญหานี้ได้โดยการแคสต์และทำลายโค้ดของคุณด้วยวิธีนั้น
Hulk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.