ทำไมวิธี 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" ในความหมายของวิธีการที่ให้การเข้าถึงฟิลด์เท่านั้น หากคุณเปลี่ยนการใช้งานภายในของผู้ให้บริการคุณเพียงแค่เปลี่ยนนิยามของวัตถุที่ส่งคืนเพื่อรองรับการเปลี่ยนแปลง คุณยังคงปกป้องโค้ดภายนอกที่ใช้อ็อบเจ็กต์ผ่านอินเทอร์เฟซ