ใช้สาธารณะขั้นสุดท้ายแทนที่จะได้รับส่วนตัว


51

ฉันเห็น POJO ที่ไม่เปลี่ยนรูปที่สุดเขียนแบบนี้:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

แต่ฉันมักจะเขียนแบบนี้:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

โปรดสังเกตว่าการอ้างอิงนั้นเป็นที่สิ้นสุดดังนั้น Object ยังคงไม่เปลี่ยนรูป มันช่วยให้ฉันเขียนโค้ดน้อยลงและอนุญาตการเข้าถึงที่สั้นลง (5 ตัวอักษร: getและ())

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

มีข้อเสียใด ๆ ที่ฉันหายไปหรือไม่?

แก้ไข

ฉันคิดว่าข้อเสียอีกอย่างที่ฉันขาดไปคือไลบรารี่อนุกรมที่ใช้การสะท้อนวิธีการที่ขึ้นต้นด้วยgetหรือisนั่นเป็นวิธีปฏิบัติที่แย่มาก ...


อะไรกันเนี่ย? finalไม่ทำให้ตัวแปรเป็นวัตถุที่ไม่เปลี่ยนรูป ปกติฉันจะใช้การออกแบบที่ฉันกำหนดfinalเขตข้อมูลก่อนที่จะสร้างการโทรกลับเพื่อให้การเรียกกลับสามารถเข้าถึงเขตข้อมูลเหล่านั้น แน่นอนสามารถเรียกใช้วิธีการทั้งหมดรวมถึงsetXวิธีการใดก็ได้
Tomáš Zato

@ TomášZatoไม่มีวิธี setX อยู่บนString, หรือint MyObjectในรุ่นแรกfinalเป็นเพียงเพื่อให้แน่ใจว่าวิธีการอื่น ๆ กว่าสร้างภายในชั้นเรียนไม่พยายามที่จะbar = 7;ยกตัวอย่างเช่น ในรุ่นที่สองเป็นสิ่งที่จำเป็นเพื่อป้องกันไม่ให้ผู้บริโภคจากการทำ:final MyObject x = new MyObject("hi", 5); x.bar = 7;
Cory Kendall

1
" หมายเหตุการอ้างอิงObjectของคุณถือเป็นที่สิ้นสุดดังนั้นสิ่งที่ยังไม่เปลี่ยนรูปแบบ " ก็คือการทำให้เข้าใจผิด - ด้วยวิธีนี้ดูเหมือนว่าคุณคิดว่าสิ่งใดfinal Objectเป็นสิ่งที่ไม่เปลี่ยนรูปซึ่งไม่ได้เป็นเช่นนั้น ขอโทษที่เข้าใจผิด.
Tomáš Zato

3
หากค่าพื้นฐานได้ไม่แน่นอนที่ + ส่วนตัว accessors ถนนจะไม่ป้องกันอย่างใดอย่างหนึ่ง myObj.getFoo().setFrob(...)-
Beni Cherniavsky-Paskin

คำตอบ:


38

ข้อเสียสี่ข้อที่ฉันนึกได้:

  1. หากคุณต้องการมีรูปแบบอ่านอย่างเดียวและไม่แน่นอนของเอนทิตีเดียวกันรูปแบบทั่วไปคือการมีคลาสเอนทิตี้ที่ไม่เปลี่ยนรูปที่เปิดเผยเพียง accessors ที่มีตัวแปรสมาชิกที่ได้รับการป้องกันจากนั้นสร้าง MutableEntity ซึ่งขยายมันและเพิ่มตัวตั้งค่า เวอร์ชันของคุณป้องกันไว้
  2. การใช้ getters และ setters ปฏิบัติตามระเบียบการ JavaBeans หากคุณต้องการใช้คลาสของคุณเป็นถั่วในเทคโนโลยีที่อิงตามคุณสมบัติเช่น JSTL หรือ EL คุณจะต้องเปิดเผยตัวรับสัญญาณสาธารณะ
  3. หากคุณต้องการเปลี่ยนการใช้งานเพื่อรับค่าหรือค้นหาในฐานข้อมูลคุณจะต้องสร้างรหัสลูกค้าใหม่ วิธีการเข้าถึง / การเปลี่ยนแปลงช่วยให้คุณสามารถเปลี่ยนการใช้งานเท่านั้น
  4. ความประหลาดใจอย่างน้อย - เมื่อฉันเห็นตัวแปรอินสแตนซ์สาธารณะฉันจะค้นหาทันทีว่าใครจะกลายพันธุ์และกังวลว่าฉันกำลังเปิดกล่องแพนโดร่าเพราะการห่อหุ้มหายไป http://en.wikipedia.org/wiki/Principle_of_least_astonishment

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


3
คำตอบที่ดี; ฉันไม่เห็นด้วยกับบางคน แต่ฉันไม่แน่ใจว่าไซต์นี้เป็นฟอรัมที่ดีที่สุดสำหรับการสนทนา ในระยะสั้น 1) ฉันสามารถทำลายผู้อื่นโดยใช้รูปแบบที่ไม่แน่นอนที่พวกเขาคิดว่ามันไม่เปลี่ยนรูป (บางทีฉันควรจะเพิ่มขั้นสุดท้ายให้กับชั้นเรียนในตัวอย่างของฉัน) 2) ฉันเห็นด้วย 3) ฉันจะเถียงว่าไม่มี POJO อีกต่อไป กรณี 4) ฉันเห็นด้วยตอนนี้ แต่หวังว่าการอภิปรายเช่นนี้สามารถเปลี่ยนสิ่งที่น่าอัศจรรย์น้อยที่สุด :)
Cory Kendall

2
5) คุณไม่สามารถเปิดเผยคลาส / ประเภทที่ไม่แน่นอนเช่นอาร์เรย์ได้อย่างปลอดภัย วิธีที่ปลอดภัยคือส่งคืนสำเนาจากผู้ทะเยอทะยานในแต่ละครั้ง
Clockwork-Muse

@ Clockwork-Muse คุณสามารถทำได้ถ้าคุณคิดว่าคนไม่ใช่คนโง่ เมื่อเข้าถึง someObj.thisList.add () จะเห็นได้ชัดว่าคุณกำลังแก้ไขสถานะวัตถุอื่น ๆ ด้วย someObj.getThisList (). เพิ่ม () มันไม่เป็นที่รู้จัก คุณต้องถามเอกสารหรือค้นหาแหล่งที่มา แต่จากการประกาศวิธีการเพียงอย่างเดียวมันเป็นไปไม่ได้ที่จะบอก
kritzikratzi

2
@kritzikratzi - ปัญหาคือคุณไม่สามารถรับประกันได้ว่ารหัสของคุณจะไม่ทำงานเป็นคนงี่เง่า ในบางจุดมันจะ (ซึ่งอาจจบลงด้วยการเป็นตัวของตัวเอง!) และความประหลาดใจอาจเป็นปัญหาใหญ่
Clockwork-Muse

1
ทีนี้การปล่อยให้รูปแบบที่ไม่แน่นอนใด ๆ ที่สืบทอดจากรูปแบบที่ไม่เปลี่ยนรูปนั้นผิด โปรดจำไว้ว่าประเภทที่ไม่เปลี่ยนรูปแบบนั้นให้การรับรองว่า "ฉันจะไม่เปลี่ยนแปลงเลย"
Deduplicator

26

กำจัด getters / setters ด้วยและคุณก็สบายดี!

นี่เป็นหัวข้อที่ถกเถียงกันอย่างมากในหมู่โปรแกรมเมอร์ Java

อย่างไรก็ตามมีสองคำสั่งที่ฉันใช้ตัวแปรสาธารณะแทน (!) ของ getters / setters:

  1. สาธารณะขั้นสุดท้ายสำหรับฉันสัญญาณนี้ "ฉันไม่เปลี่ยนรูป" ดีกว่าแค่ทะเยอทะยาน IDEs ส่วนใหญ่จะระบุตัวแก้ไขสุดท้ายด้วย 'F' ในระหว่างการเติมข้อมูลอัตโนมัติ แตกต่างจาก getters / setters ที่คุณต้องค้นหาการขาดของ setXXX
  2. สาธารณะไม่ใช่ครั้งสุดท้ายฉันรักสิ่งนี้สำหรับคลาสข้อมูล ฉันเพิ่งเปิดเผยทุกฟิลด์สาธารณะ ไม่มี getters, setters, constructors ไม่มีอะไร. น้อยกว่าหนึ่ง pojo สำหรับฉันแล้วจะส่งสัญญาณทันที "ดูสิฉันเป็นใบ้ฉันเก็บข้อมูลนั่นคือทั้งหมดที่คุณต้องใส่ข้อมูลที่ถูกต้องไว้ในตัว" Gson / JAXB / ฯลฯ จัดการกับคลาสเหล่านี้ได้ดี พวกเขามีความสุขที่จะเขียน ไม่มีข้อสงสัยเกี่ยวกับวัตถุประสงค์หรือความสามารถของพวกเขา และที่สำคัญที่สุด: คุณรู้ว่าไม่มีผลข้างเคียงเมื่อคุณเปลี่ยนตัวแปร IMHO ส่งผลให้ตัวแบบข้อมูลที่รัดกุมมากมีความคลุมเครือเล็กน้อยในขณะที่ตัวรับและตัวตั้งมีปัญหาใหญ่นี้ซึ่งบางครั้งเวทย์มนตร์ก็เกิดขึ้นภายใน

5
ดีที่จะเห็นสติบางครั้งในชั่วขณะ :)
นาวิน

2
จนกว่าจะถึงวันที่โมเดลของคุณวิวัฒนาการและหนึ่งในทุ่งนาเก่าสามารถรับได้จากที่อื่น เนื่องจากคุณต้องการรักษาความเข้ากันได้ย้อนหลังฟิลด์เก่าจะต้องคงอยู่ แต่แทนที่จะเปลี่ยน getter และ setter code คุณจะต้องเปลี่ยนทุก ๆ ที่ที่ฟิลด์เก่านี้ถูกใช้เพื่อให้แน่ใจว่ามันเป็นไปตามการแสดงรูปแบบใหม่ ง่ายต่อการเขียน .. ใช่แน่นอนว่า ... ฝันร้ายที่ต้องรักษาในระยะยาว ... นั่นคือสาเหตุที่เรามีผู้บุกรุก / ผู้ตั้งค่าหรือดีกว่าใน C # ผู้เข้าถึงคุณสมบัติ
Newtopian

9

ในคำพูดของคนธรรมดา:

  • คุณละเมิด encapsulationเพื่อบันทึกรหัสสองสามบรรทัด นั่นเอาชนะจุดประสงค์ของ OOD
  • รหัสลูกค้าจะเชื่อมโยงกับชื่อสมาชิกชั้นเรียนของคุณได้ยาก การมีเพศสัมพันธ์ไม่ดี จุดประสงค์ทั้งหมดของ OOD คือป้องกันการมีเพศสัมพันธ์
  • นอกจากนี้คุณยังมั่นใจได้ว่าชั้นเรียนของคุณจะไม่ต้องเปลี่ยนแปลง สิ่งต่าง ๆ เปลี่ยนไป การเปลี่ยนแปลงเป็นสิ่งเดียวที่คงที่

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

4
@Kalha คุณสับสน "encapsulation ละเมิด" กับ "รหัสล้มเหลวในการรวบรวมและต้องเสียเวลาแก้ไขมัน" หนึ่งเกิดขึ้นก่อน ครั้งที่สองเกิดขึ้นในภายหลัง เพื่อให้ชัดเจน: encapsulation ถูกละเมิดแล้วในปัจจุบัน อวัยวะในชั้นเรียนของคุณจะไม่ถูกห่อหุ้มในปัจจุบันอีกต่อไป แต่รหัสลูกค้าจะแตกหักในอนาคตหากการเปลี่ยนแปลงชั้นเรียนในอนาคตเนื่องจากการห่อหุ้มถูกทำลายในอดีต มี "การแบ่ง" สองแบบที่แตกต่างกันในวันนี้การห่อหุ้มรหัสลูกค้าในวันพรุ่งนี้เนื่องจากไม่มีการห่อหุ้ม
Tulains Córdova

8
ในทางเทคนิคเมื่อคุณใช้ getters / etc รหัสลูกค้าจะยากคู่กับชื่อวิธีแทน ดูว่าห้องสมุดหลายแห่งต้องรวมวิธีการแบบเก่าด้วยแท็ก / คำอธิบายประกอบ "ที่เลิกใช้แล้ว" เพราะรหัสที่เก่ากว่านั้นยังคงไว้ใจได้ (และบางคนอาจยังใช้พวกเขาเพราะพวกเขาพบว่าทำงานได้ง่ายขึ้น แต่คุณไม่ใช่ ควรทำเช่นนั้น)
JAB

5
ในทางปฏิบัติ: โปรแกรมเมอร์เขียนชื่อฟิลด์ส่วนตัวบ่อยครั้งโดยไม่เปลี่ยนชื่อ getters / setters สาธารณะ จากสิ่งที่ฉันสามารถบอกได้ว่ามีการประชุมทั่วทั้งชุมชนเพื่อตั้งชื่อพวกเขาเหมือนกันแม้ว่าการประชุมนั้นอาจเกิดจากเครื่องมือ IDE ที่สร้าง getters และ setters จากชื่อฟิลด์เท่านั้น เชิงทฤษฎี: จากมุมมองเชิงวัตถุแบบจำลองอย่างไรก็ตามสิ่งที่สาธารณะเป็นส่วนหนึ่งของสัญญาสาธารณะและไม่ใช่รายละเอียดการนำไปปฏิบัติภายใน ดังนั้นหากมีคนตัดสินใจที่จะเผยแพร่ภาคสนามสุดท้ายพวกเขาไม่ได้พูดว่านี่คือสัญญาที่พวกเขาสัญญาว่าจะทำให้สำเร็จหรือไม่?
Thomas Jung

3
@ user949300 บอกฉันหน่อยว่าฉันเรียนอะไรได้
Tulains Córdova

6

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

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