ทำไมวิธี getter และ setter ถึงชั่วร้าย

ฉันไม่ได้ตั้งใจจะเริ่มซีรีส์ "is evil" แต่ผู้อ่านหลายคนขอให้ฉันอธิบายว่าทำไมฉันถึงบอกว่าคุณควรหลีกเลี่ยง get / set method ในคอลัมน์ของเดือนที่แล้ว "Why expands Is Evil"

แม้ว่าเมธอด getter / setter จะเป็นสิ่งที่พบเห็นได้ทั่วไปใน Java แต่ก็ไม่ได้เป็นแบบเชิงวัตถุ (OO) โดยเฉพาะ ในความเป็นจริงมันสามารถทำลายความสามารถในการบำรุงรักษาของโค้ดของคุณ ยิ่งไปกว่านั้นการปรากฏตัวของวิธี getter และ setter จำนวนมากเป็นธงสีแดงที่โปรแกรมไม่จำเป็นต้องได้รับการออกแบบมาอย่างดีจากมุมมองของ OO

บทความนี้อธิบายว่าเหตุใดคุณจึงไม่ควรใช้ getter และ setter (และเมื่อใดที่คุณสามารถใช้ได้) และแนะนำวิธีการออกแบบที่จะช่วยให้คุณหลุดพ้นจากความคิดของ getter / setter

เกี่ยวกับลักษณะของการออกแบบ

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

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

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

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

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

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

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

ข้อมูลที่เป็นนามธรรม

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

หากต้องการดูสาเหตุให้พิจารณาว่าอาจมีการโทรไปยังgetX()เมธอด1,000 ครั้งในโปรแกรมของคุณและการโทรแต่ละครั้งจะถือว่าค่าส่งคืนเป็นประเภทเฉพาะ คุณอาจจัดเก็บgetX()ค่าที่ส่งคืนไว้ในตัวแปรท้องถิ่นตัวอย่างเช่นและประเภทตัวแปรนั้นต้องตรงกับประเภทค่าตอบแทน หากคุณต้องการเปลี่ยนวิธีการใช้งานวัตถุในลักษณะที่ประเภทของ X เปลี่ยนไปแสดงว่าคุณมีปัญหาอย่างมาก

ถ้า X เป็นintแต่ตอนนี้ต้องเป็น a longคุณจะได้รับ 1,000 ข้อผิดพลาดในการคอมไพล์ หากคุณแก้ไขปัญหาไม่ถูกต้องโดยการส่งค่าส่งคืนไปintรหัสจะคอมไพล์อย่างสมบูรณ์ แต่จะไม่ทำงาน (ค่าที่ส่งคืนอาจถูกตัดทอน) คุณต้องแก้ไขโค้ดที่อยู่รอบ ๆ การโทร 1,000 ครั้งเหล่านั้นเพื่อชดเชยการเปลี่ยนแปลง ฉันไม่อยากทำงานมากขนาดนั้น

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

หากคุณสร้างตัวแปรอินสแตนซ์publicคุณจะไม่สามารถเปลี่ยนฟิลด์ได้เนื่องจากคลาสมีการเปลี่ยนแปลงตลอดเวลาเพราะคุณจะทำลายโค้ดภายนอกที่ใช้ฟิลด์ คุณไม่ต้องการค้นหาการใช้คลาส 1,000 ครั้งเพียงเพราะคุณเปลี่ยนคลาสนั้น

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

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

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

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

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

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

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

วาดตัวเอง

การแบ่งส่วนหนึ่งของการห่อหุ้มฟิลด์แบบเต็มอยู่ในโครงสร้างส่วนติดต่อผู้ใช้ (UI) หากคุณไม่สามารถใช้ accessors คุณจะไม่สามารถมีคลาสตัวสร้าง UI เรียกใช้getAttribute()เมธอดได้ แต่คลาสจะมีองค์ประกอบเช่นdrawYourself(...)เมธอด

แน่นอนว่าgetIdentity()เมธอดยังสามารถใช้งานได้หากส่งคืนอ็อบเจ็กต์ที่ใช้Identityอินเทอร์เฟซ อินเทอร์เฟซนี้ต้องมีเมธอดdrawYourself()(หรือ give-me-a- JComponent-that-represent-your-identity) แม้ว่าจะgetIdentityเริ่มต้นด้วย "get" แต่ก็ไม่ใช่ accessor เพราะไม่เพียงส่งคืนฟิลด์ ส่งคืนวัตถุที่ซับซ้อนซึ่งมีพฤติกรรมที่สมเหตุสมผล แม้ว่าฉันจะมีIdentityสิ่งของ แต่ฉันก็ยังไม่รู้ว่าตัวตนถูกแสดงภายในอย่างไร

แน่นอนdrawYourself()กลยุทธ์หมายความว่าฉัน (อ้าปากค้าง!) ใส่รหัส UI ในตรรกะทางธุรกิจ พิจารณาว่าจะเกิดอะไรขึ้นเมื่อความต้องการของ UI เปลี่ยนไป สมมติว่าฉันต้องการแสดงแอตทริบิวต์ในลักษณะที่แตกต่างไปจากเดิมอย่างสิ้นเชิง วันนี้ "ตัวตน" คือชื่อ; พรุ่งนี้เป็นชื่อและหมายเลขประจำตัว วันหลังจากนั้นเป็นชื่อหมายเลขบัตรประชาชนและรูปภาพ ฉัน จำกัด ขอบเขตของการเปลี่ยนแปลงเหล่านี้ไว้ที่เดียวในโค้ด ถ้าฉันมีJComponentคลาส Give -me-a- ที่แสดงถึงตัวตนของคุณฉันจะแยกวิธีแสดงอัตลักษณ์จากส่วนที่เหลือของระบบ

โปรดทราบว่าฉันยังไม่ได้ใส่รหัส UI ใด ๆ ลงในตรรกะทางธุรกิจ ฉันได้เขียนเลเยอร์ UI ในรูปแบบ AWT (Abstract Window Toolkit) หรือ Swing ซึ่งเป็นเลเยอร์นามธรรมทั้งคู่ รหัส UI จริงอยู่ในการใช้งาน AWT / Swing นั่นคือจุดรวมของเลเยอร์นามธรรม - เพื่อแยกตรรกะทางธุรกิจของคุณออกจากกลไกของระบบย่อย ฉันสามารถพอร์ตไปยังสภาพแวดล้อมแบบกราฟิกอื่นได้อย่างง่ายดายโดยไม่ต้องเปลี่ยนรหัสดังนั้นปัญหาเดียวก็คือความยุ่งเหยิงเล็กน้อย คุณสามารถกำจัดความยุ่งเหยิงนี้ได้อย่างง่ายดายโดยการย้ายรหัส UI ทั้งหมดไปยังคลาสภายใน (หรือโดยใช้รูปแบบการออกแบบFaçade)

JavaBeans

คุณอาจคัดค้านโดยพูดว่า "แล้ว JavaBeans ล่ะ" แล้วพวกเขาล่ะ? คุณสามารถสร้าง JavaBeans ได้อย่างแน่นอนโดยไม่ต้องใช้ getters และ setters BeanCustomizer, BeanInfoและBeanDescriptorชั้นเรียนที่มีอยู่ทั้งหมดสำหรับตรงจุดประสงค์นี้ นักออกแบบข้อมูลจำเพาะ JavaBean โยนสำนวน getter / setter ลงในภาพเพราะพวกเขาคิดว่ามันจะเป็นวิธีง่ายๆในการสร้าง bean อย่างรวดเร็วซึ่งเป็นสิ่งที่คุณทำได้ในขณะที่เรียนรู้วิธีการทำอย่างถูกต้อง น่าเสียดายที่ไม่มีใครทำอย่างนั้น

Accessors ถูกสร้างขึ้นเพื่อใช้ในการแท็กคุณสมบัติบางอย่างเพื่อให้โปรแกรมสร้าง UI หรือสิ่งที่เทียบเท่าสามารถระบุคุณสมบัติเหล่านั้นได้ คุณไม่ควรเรียกวิธีการเหล่านี้ด้วยตัวเอง มีไว้สำหรับเครื่องมืออัตโนมัติที่จะใช้ เครื่องมือนี้ใช้ API การวิปัสสนาในClassคลาสเพื่อค้นหาวิธีการและคาดการณ์การมีอยู่ของคุณสมบัติบางอย่างจากชื่อวิธีการ ในทางปฏิบัติสำนวนที่ใช้วิปัสสนานี้ยังไม่ได้ผล มันทำให้โค้ดซับซ้อนและมีขั้นตอนมากเกินไป โปรแกรมเมอร์ที่ไม่เข้าใจข้อมูลที่เป็นนามธรรมเรียกตัวเข้าถึงและด้วยเหตุนี้โค้ดจึงไม่สามารถบำรุงรักษาได้ ด้วยเหตุนี้คุณลักษณะข้อมูลเมตาจะรวมอยู่ใน Java 1.5 (ครบกำหนดกลางปี ​​2547) แทนที่จะเป็น:

ทรัพย์สิน int ส่วนตัว int สาธารณะ getProperty () {return property; } โมฆะสาธารณะ setProperty (ค่า int} {คุณสมบัติ = ค่า;}

คุณจะสามารถใช้สิ่งต่างๆเช่น:

ทรัพย์สินส่วนตัว @property int; 

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

Accessor ตกลงเมื่อใด

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