ตรรกะเท่าไหร่ใน Getters


46

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

กระนั้นฉันก็มั่นใจว่ามีหลายสิ่งที่สามารถซ่อนอยู่ในตัวเชื่อมต่อและตัวตั้งเพื่อปกป้องผู้ใช้ / โปรแกรมเมอร์จากรายละเอียดการใช้งาน

ตัวอย่างของสิ่งที่ฉันทำ:

public List<Stuff> getStuff()
{
   if (stuff == null || cacheInvalid())
   {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

ตัวอย่างของการทำงานให้ฉันทำสิ่งต่าง ๆ (พวกเขาอ้างว่า 'รหัสสะอาด' จากลุงบ๊อบ):

public List<Stuff> getStuff()
{
    return stuff;
}

public void loadStuff()
{
    stuff = getStuffFromDatabase();
}

ตรรกะมีความเหมาะสมใน setter / getter หรือไม่ การใช้ getters และ setters ที่ว่างเปล่าคืออะไรยกเว้นการละเมิดการซ่อนข้อมูล


6
ดูเหมือนว่า tryGetStuff () กับฉัน ...
Bill Michell

16
นี่ไม่ใช่ 'ทะเยอทะยาน' คำนี้ใช้สำหรับผู้เข้าถึงการอ่านของคุณสมบัติไม่ใช่วิธีที่คุณใส่ 'รับ' ในชื่อโดยไม่ตั้งใจ
Boris Yankov

6
ฉันไม่รู้ว่าตัวอย่างที่สองนั้นเป็นตัวอย่างที่ดีของหนังสือโค้ดสะอาดที่คุณพูดถึงหรือคนที่ติดปลายผิดเกี่ยวกับเรื่องนี้ แต่สิ่งหนึ่งที่ระเบียบไม่เปราะคือรหัสที่สะอาด
Jon Hanna

@ BorisYankov ดี ... วิธีที่สองคือ public List<Stuff> getStuff() { return stuff; }
R. Schmitz

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

คำตอบ:


71

วิธีการทำงานบอกให้คุณทำสิ่งต่าง ๆ เป็นง่อย

ตามกฎของหัวแม่มือวิธีที่ฉันทำสิ่งต่าง ๆ มีดังนี้: หากการได้รับสิ่งของนั้นถูกกว่าการคำนวณ (หรือถ้าโอกาสส่วนใหญ่ที่จะพบได้ในแคช) จากนั้นสไตล์ของ getStuff () ของคุณก็ใช้ได้ หากการรับสิ่งของเป็นที่รู้จักกันว่ามีค่าใช้จ่ายในการคำนวณสูงดังนั้นการโฆษณาที่มีค่าใช้จ่ายจำเป็นต้องมีในส่วนต่อประสานจากนั้นฉันจะไม่เรียกมันว่า getStuff () ฉันจะเรียกมันว่า calculStuff () หรืออะไรทำนองนั้น จะมีงานให้ทำ

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


23
+1 สำหรับการกล่าวถึงลำดับความซับซ้อนของการดำเนินการ ในฐานะที่เป็นวิธีแก้ปัญหาบางทีงานจะขอให้ฉันเรียก loadStuff () ในตัวสร้างเสมอ แต่นั่นก็ไม่ดีเหมือนกันเพราะมันหมายความว่ามันจะต้องโหลดเสมอ ในตัวอย่างแรกข้อมูลจะถูกโหลดอย่างเกียจคร้านเฉพาะเมื่อจำเป็นซึ่งดีเท่าที่จะเป็นได้
เรน

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

3
ถ้ามันสามารถล้มเหลว - มันไม่ทะเยอทะยาน ในกรณีนี้จะเกิดอะไรขึ้นถ้าลิงค์ DB ไม่ทำงาน?
Martin Beckett

6
+1, ฉันตกใจเล็กน้อยกับจำนวนคำตอบที่ผิดที่โพสต์ Getters / Setters มีอยู่เพื่อซ่อนรายละเอียดการนำไปใช้มิฉะนั้นตัวแปรควรถูกเปิดเผยสู่สาธารณะ
Izkata

2
อย่าลืมว่าการloadStuff()เรียกใช้ฟังก์ชั่นก่อนหน้าที่จะgetStuff()ทำยังหมายความว่าชั้นเรียนไม่ถูกต้องที่จะสรุปสิ่งที่เกิดขึ้นภายใต้ประทุน
rjzii

23

ลอจิกในทะเยอทะยานดีอย่างสมบูรณ์

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

ในทางกลับกัน ORM ส่วนใหญ่รองรับการโหลดคอลเลกชันที่ขี้เกียจซึ่งเป็นสิ่งที่คุณกำลังทำอยู่


18

ฉันคิดว่าตาม 'รหัสสะอาด' ควรแบ่งให้มากที่สุดเท่าที่เป็นไปได้เช่น:

public List<Stuff> getStuff() {
   if (hasStuff()) {
       return stuff;
   }
   loadStuff();
   return stuff;
}

private boolean hasStuff() {
    if (stuff == null) {
       return false;
    }
    if (cacheInvalid()) {
       return false;        
    }
    return true;
} 

private void loadStuff() {
    stuff = getStuffFromDatabase();
}

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

public List<Stuff> getStuff() {
   if (stuff == null || cacheInvalid()) {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

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


8
-1 อาการปวดหัวที่แท้จริงจะเกิดขึ้นเมื่อผู้โทรติดอยู่เพื่อหาสาเหตุที่การโทรเข้าแบบธรรมดาทำให้การเข้าถึงฐานข้อมูลช้าลง
Domenic

14
@Domenic: การเข้าถึงฐานข้อมูลจะต้องทำต่อไปคุณไม่ได้บันทึกประสิทธิภาพของใครก็ตามโดยไม่ทำ หากคุณต้องการสิ่งนี้List<Stuff>มีเพียงวิธีเดียวเท่านั้นที่จะได้รับ
DeadMG

4
@ lukas: ขอบคุณฉันไม่จำเทคนิคทั้งหมดที่ใช้ในรหัส 'Clean' เพื่อสร้างบิตของรหัสได้อีกหนึ่งบรรทัดอีกต่อไป ;-) แก้ไขเดี๋ยวนี้
Joonas Pulakka

2
คุณกำลังใส่ร้ายโรเบิร์ตมาร์ติน เขาจะไม่ขยายความแตกต่างบูลีนอย่างง่ายให้เป็นฟังก์ชันเก้าบรรทัด ฟังก์ชั่นของคุณhasStuffตรงข้ามกับรหัสที่สะอาด
kevin cline

2
ฉันอ่านจุดเริ่มต้นของคำตอบนี้และฉันจะข้ามมันไปโดยคิดว่า "มีผู้นมัสการหนังสือคนอื่น" และ "แน่นอนว่านี่เป็นเรื่องไร้สาระที่สมบูรณ์" ส่วนหนึ่งจับตาฉัน พูดได้ดี! C -: =
Mike Nakis

8

พวกเขาบอกฉันว่าควรมีเหตุผลน้อยที่สุดใน getters และ setters

จะต้องมีเหตุผลมากเท่าที่จำเป็นเพื่อตอบสนองความต้องการของชั้นเรียน การตั้งค่าส่วนตัวของฉันมีค่าน้อยที่สุด แต่เมื่อต้องบำรุงรักษารหัสคุณมักจะต้องออกจากส่วนต่อประสานเดิมกับ getters / setters ที่มีอยู่เดิม แต่ใช้ตรรกะจำนวนมากเพื่อแก้ไขตรรกะทางธุรกิจใหม่ (ตัวอย่างเช่นลูกค้า "ทะเยอทะยานในสภาพแวดล้อมที่โพสต์ 911 ต้องพบ" รู้จักลูกค้าของคุณ "และข้อบังคับของ OFACรวมกับนโยบายของ บริษัท ที่ห้ามไม่ให้ลูกค้าจากบางประเทศปรากฏตัวเช่นคิวบาหรืออิหร่าน

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


6

คุณพูดถูกเพื่อนร่วมงานของคุณผิด

ลืมกฎง่ายๆของทุกคนเกี่ยวกับวิธีการรับควรหรือไม่ควรทำ ชั้นควรนำเสนอสิ่งที่เป็นนามธรรม stuffระดับของคุณมีสามารถอ่านได้ ในจาวามันเป็นเรื่องธรรมดาที่จะใช้วิธีการ 'รับ' เพื่ออ่านคุณสมบัติ พันล้านของเส้นกรอบได้รับการเขียนคาดหวังว่าจะอ่านโดยโทรstuff getStuffหากคุณตั้งชื่อฟังก์ชั่นของคุณfetchStuffหรือสิ่งอื่นใดนอกจากgetStuffคลาสของคุณจะไม่เข้ากับกรอบงานเหล่านั้นทั้งหมด

คุณอาจชี้ให้พวกเขาไปที่ Hibernate โดยที่ 'getStuff ()' สามารถทำสิ่งที่ซับซ้อนมาก ๆ และโยน RuntimeException เมื่อเกิดความล้มเหลว


Hibernate เป็น ORM ดังนั้นแพ็คเกจจึงแสดงเจตนา ความตั้งใจนี้ไม่เข้าใจได้ง่ายหากบรรจุภัณฑ์นั้นไม่ใช่ ORM
FMJaguar

@FJJaguar: เข้าใจได้ง่ายมาก การดำเนินการฐานข้อมูลบทคัดย่อของ Hibernate เพื่อนำเสนอเครือข่ายของวัตถุ stuffสหกรณ์มีการสรุปการดำเนินงานฐานข้อมูลที่จะนำเสนอวัตถุที่มีคุณสมบัติการตั้งชื่อ ทั้งซ่อนรายละเอียดเพื่อให้ง่ายต่อการเขียนรหัสการโทร
kevin cline

หากคลาสนั้นเป็นคลาส ORM ความตั้งใจจะแสดงออกมาแล้วในบริบทอื่น: คำถามยังคงอยู่: "โปรแกรมเมอร์คนอื่นรู้ผลข้างเคียงของการโทรทะลวงได้อย่างไร" หากโปรแกรมมีคลาส 1k และ 10k getters นโยบายที่อนุญาตให้เรียกใช้ฐานข้อมูลใด ๆ ของพวกเขาอาจเป็นปัญหา
FMJaguar

4

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

List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

ตรงข้ามกับ:

myObject.load();
List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

หรือแย่ลง:

myObject.loadNames();
List<String> names = clientRoster.getNames();
myOjbect.loadEmails();
List<String> emails = clientRoster.getEmails();

ซึ่งเพียงแค่ทำให้รหัสอื่นซ้ำซ้อนมากขึ้นและอ่านยากขึ้นเพราะคุณต้องเริ่มลุยผ่านการโทรที่คล้ายกันทั้งหมด นอกจากนี้การเรียกฟังก์ชั่นโหลดเดอร์หรือการแบ่งที่คล้ายกันมีจุดประสงค์ทั้งหมดแม้กระทั่งการใช้ OOP ซึ่งคุณจะไม่ถูกแยกออกจากรายละเอียดการใช้งานของวัตถุที่คุณกำลังทำงานอยู่อีกต่อไป หากคุณมีclientRosterวัตถุคุณไม่ควรต้องสนใจว่าจะgetNamesทำงานอย่างไรเช่นเดียวกับถ้าคุณต้องโทรหาloadNamesคุณควรจะรู้ว่านั่นgetNamesเป็นList<String>ชื่อของลูกค้า

ดังนั้นดูเหมือนว่าปัญหาจะเกี่ยวกับความหมายและชื่อที่ดีที่สุดสำหรับฟังก์ชั่นในการรับข้อมูล หาก บริษัท (และอื่น ๆ ) มีปัญหากับgetและsetคำนำหน้าดังนั้นวิธีการเรียกฟังก์ชั่นบางอย่างเช่นretrieveNamesแทน? มันบอกว่าเกิดอะไรขึ้น แต่ไม่ได้บอกเป็นนัยว่าการดำเนินการจะเป็นไปอย่างทันทีทันใดอย่างที่คาดgetไว้

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


2

การเรียกผู้ทะเยอทะยานควรแสดงพฤติกรรมเดียวกันกับการอ่านฟิลด์

  • มันควรจะถูกเพื่อดึงค่า
  • หากคุณตั้งค่าด้วย setter แล้วอ่านด้วย getter ค่านั้นควรจะเหมือนกัน
  • การรับค่าควรไม่มีผลข้างเคียง
  • ไม่ควรโยนข้อยกเว้น

2
ฉันไม่เห็นด้วยกับเรื่องนี้อย่างสมบูรณ์ ฉันยอมรับว่ามันไม่ควรมีผลข้างเคียง แต่ฉันคิดว่ามันเป็นเรื่องที่สมบูรณ์แบบที่จะนำไปใช้ในวิธีที่แตกต่างจากสนาม การดู. Net BCL นั้น InvalidOperationException ถูกใช้อย่างกว้างขวางเมื่อมองไปที่ getters นอกจากนี้โปรดดู MikeNakis ตอบคำถามเกี่ยวกับลำดับการปฏิบัติการ
Max

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

1
@TMN: ในสถานการณ์กรณีที่ดีที่สุดคลาสควรจัดระเบียบในลักษณะที่ไม่จำเป็นต้องเรียกใช้ตัวดำเนินการที่สามารถเรียกใช้ข้อยกเว้นได้ การลดสถานที่ที่สามารถลดข้อยกเว้นลงได้จะทำให้ประหลาดใจน้อยลง
hugomg

8
foo.setAngle(361); bar = foo.getAngle()ผมจะไม่เห็นด้วยกับจุดที่สองด้วยตัวอย่างที่เฉพาะเจาะจง: barอาจเป็นได้361แต่มันอาจถูกต้องเช่นกัน1หากมุมถูกผูกไว้กับช่วง
zzzzBov

1
-1 (1) เป็นราคาถูกในตัวอย่างนี้ - หลังจากโหลดขี้เกียจ (2) ในปัจจุบันไม่มี "setter" ในตัวอย่าง แต่ถ้ามีคนเพิ่มอีกหนึ่งหลังและมันเพิ่งตั้งค่าstuffgetter จะส่งคืนค่าเดียวกัน (3) การโหลดแบบ Lazy ดังที่แสดงในตัวอย่างจะไม่สร้างผลข้างเคียง (4) เป็นที่ถกเถียงกันอาจเป็นจุดที่ถูกต้องเนื่องจากการแนะนำ "การโหลดแบบขี้เกียจ" หลังจากนั้นสามารถเปลี่ยนสัญญา API เดิม - แต่ต้องดูที่สัญญานั้นเพื่อทำการตัดสินใจ
Doc Brown

2

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

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

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

เมื่อเริ่มต้นตรงที่อาจเกิดขึ้นหรือไม่อาจมีความสำคัญ

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

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

ที่กล่าวว่าการเริ่มต้น Lazy ทำให้รู้สึกอย่างมากในสถานการณ์อื่น ๆ อีกมากมาย ดังนั้นมักจะเกิดขึ้นในการเขียนโปรแกรมมันยากที่จะต้มลงไปในกฎมันลงมาเพื่อรหัสที่เป็นรูปธรรม


0

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

หากสถานที่ให้บริการ / ฟังก์ชั่นของคุณกำลังทำในสิ่งที่ชื่อพูดแล้วก็ไม่มีที่ว่างเหลือสำหรับภาวะแทรกซ้อน การทำงานร่วมกันเป็น IMO สำคัญ


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

แน่นอนว่า ... ผมก็แค่บอกว่าถ้าคุณทำในสิ่งที่ชื่อของรัฐที่ไม่ควรมีปัญหามากมาย ... แต่สิ่งที่คุณพูดเป็นความจริง absolutly ...
อีวาน Crojach Karacic

0

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


0

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

  1. ก่อนอื่นให้คิดดูว่ามีวิธีการจัดระเบียบโค้ดของคุณใหม่หรือไม่ดังนั้นการเรียกโหลดและการจัดการแคชที่จำเป็นทั้งหมดจะทำใน Constructor / initializer หากเป็นไปได้คุณสามารถสร้างคลาสที่ค่าคงที่อนุญาตให้คุณทำกับ getter แบบง่ายจากตอนที่ 2 ด้วยความปลอดภัยของ getter แบบซับซ้อนจากส่วนที่ 1 (สถานการณ์จำลองแบบ win-win)

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

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

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

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


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

@Domenic: นี่เป็นปัญหาความหมายและขึ้นอยู่กับภาษา จุดที่วัตถุนั้นเหมาะสมที่จะใช้และจัดเตรียมค่าคงที่ที่เหมาะสมหลังและหลังจากนั้นก็จะถูกสร้างขึ้นอย่างสมบูรณ์
hugomg

0

ในความคิดของฉัน Getters ไม่ควรมีเหตุผลมากมาย พวกเขาไม่ควรมีผลข้างเคียงและคุณไม่ควรได้รับการยกเว้นจากพวกเขา นอกจากว่าคุณรู้ว่ากำลังทำอะไรอยู่ ผู้ได้รับส่วนใหญ่ของฉันไม่มีเหตุผลในพวกเขาและเพียงแค่ไปที่ทุ่งนา แต่ข้อยกเว้นที่น่าสังเกตคือมี API สาธารณะที่ต้องใช้ให้ง่ายที่สุดเท่าที่จะทำได้ ดังนั้นฉันจึงมีผู้ทะเยอทะยานคนหนึ่งที่จะล้มเหลวหากไม่ได้รับผู้เรียกอีกคนหนึ่ง การแก้ไขปัญหา? บรรทัดของรหัสเช่นvar throwaway=MyGetter;ในทะเยอทะยานที่ขึ้นอยู่กับมัน ฉันไม่ได้ภูมิใจกับมัน แต่ฉันก็ยังไม่เห็นวิธีที่สะอาดกว่าที่จะทำ


0

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

อาจเหมาะสมที่จะใช้ชื่อgetCachedStuff()ผู้ทะเยอทะยานเนื่องจากไม่มีเวลาดำเนินการที่สอดคล้องกัน

cacheInvalid()การตรวจสอบ null อาจไม่จำเป็นทั้งนี้ขึ้นอยู่กับวิธีการทำงานประจำ ฉันจะไม่คาดหวังว่าแคชจะถูกต้องเว้นแต่ว่าstuffมีการเติมข้อมูลจากฐานข้อมูล


0

ตรรกะหลักที่ฉันคาดว่าจะเห็นใน getters ที่ส่งคืนรายการคือตรรกะเพื่อให้แน่ใจว่ารายการนั้นไม่สามารถเปลี่ยนแปลงได้ มันย่อมาจากตัวอย่างของคุณทั้งสองอาจทำลาย encapsulation

สิ่งที่ต้องการ:

public List<Stuff> getStuff()
{
    return Collections.unmodifiableList(stuff);
}

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


0

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

interface StuffGetter {
     public List<Stuff> getStuff();
}

class StuffComputer implements StuffGetter {
     public List<Stuff> getStuff() {
         getStuffFromDatabase()
     }
}

class StuffCacher implements StuffGetter {
     private stuffComputer; // DI this
     private Cache<List<Stuff>> cache = new Cache<>();

     public List<Stuff> getStuff() {
         if cache.hasStuff() {
             return cache.getStuff();
         }

         List<Stuffs> stuffs = stuffComputer.getStuff();
         cache.store(stuffs);
         return stuffs;
     }
}

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


-1

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


+1: ฉันเห็นด้วยกับคุณ! หากวัตถุมีความหมายเพียงเพื่อเก็บข้อมูลบางอย่างแล้วผู้ getters ควรกลับเนื้อหาปัจจุบันของวัตถุ ในกรณีนี้มันเป็นความรับผิดชอบของวัตถุอื่นในการโหลดข้อมูล หากสัญญาบอกว่าวัตถุนั้นเป็นพร็อกซีของเรคคอร์ดฐานข้อมูลดังนั้นผู้ทะเยอทะยานควรดึงข้อมูลทันที มันจะซับซ้อนมากขึ้นถ้าข้อมูลถูกโหลด แต่ไม่ทันสมัย: ควรแจ้งให้วัตถุทราบถึงการเปลี่ยนแปลงในฐานข้อมูลหรือไม่? ฉันคิดว่าไม่มีคำตอบเฉพาะสำหรับคำถามนี้
Giorgio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.