ปัญหาที่แม่นยำของการอนุญาตให้ getters คืออะไร


15

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

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

แต่ไม่มีเหตุผลที่สมเหตุสมผลสำหรับเหตุผลที่getDataเป็นสิ่งที่ไม่ดีในความเป็นจริงไม่กี่คนที่เป็นที่ถกเถียงกันอยู่ว่ามันเป็นเรื่องเกี่ยวกับความหมาย, getters อย่างดีต่อ se แต่เพียงแค่ไม่ได้ตั้งชื่อพวกเขาgetXให้ฉันนี้อย่างน้อยตลก .

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

แน่นอนว่าการอนุญาตให้ getter สำหรับสตริงที่ใช้ในการเข้ารหัสบางอย่างนั้นเกินกว่าจะเป็นใบ้ แต่ฉันกำลังพูดถึงข้อมูลที่ระบบของคุณต้องการใช้งาน บางทีข้อมูลของคุณจะถูกดึงผ่านProviderจากวัตถุ แต่ยังคงวัตถุยังคงต้องการที่จะช่วยให้Providerการทำ$provider[$object]->getDataมีวิธีรอบ ๆ มันไม่มี


ทำไมฉันขอ: ฉัน getters เมื่อใช้อย่างสมเหตุสมผลและข้อมูลที่จะถือว่าเป็น "ปลอดภัย" จะถูกพระเจ้าส่ง 99% ของ getters ฉันจะใช้ในการระบุวัตถุในขณะที่ฉันถามรหัสผ่านObject, what is your name? Object, what is your identifier?, ทุกคนที่ทำงานกับวัตถุควรรู้สิ่งเหล่านี้เกี่ยวกับวัตถุเพราะเกือบทุกอย่างเกี่ยวกับการเขียนโปรแกรมเป็นตัวตนและใครอื่นรู้ดีกว่ามันคืออะไรตัวเอง? ดังนั้นฉันจึงไม่เห็นปัญหาที่แท้จริงใด ๆ เว้นแต่ว่าคุณเป็นคนเจ้าระเบียบ

ฉันได้ดูคำถาม StackOverflow ทั้งหมดเกี่ยวกับ "ทำไม getters / setters" ไม่ดีและถึงแม้ว่าฉันยอมรับว่า setters นั้นแย่มากใน 99% ของกรณี getters ไม่ต้องได้รับการปฏิบัติเหมือนกันเพราะพวกเขาสัมผัส

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


2
คำแนะนำของฉันลืมทฤษฎี OO เป็นส่วนใหญ่ การปฏิบัติ เขียนโค้ดจำนวนมากแล้วบำรุงรักษา คุณจะได้เรียนรู้ไกลไกลมากขึ้นเกี่ยวกับสิ่งที่ทำงานได้ดีและสิ่งที่ไม่ได้เกิดจากการทำอะไรจริง ๆ แล้วกลับไปที่อีกไม่กี่เดือนต่อมา
jpmc26

1
@ jpmc26 มีอะไรในแม่ของ .... ฉันไม่เคยรู้เลยว่าไม่มีใครทำ OOP ได้อย่างถูกต้องฉันมีความคิดเรื่องการห่อหุ้มที่เกี่ยวข้องกับเขตข้อมูลของวัตถุอย่างเคร่งครัด แต่วัตถุนั้นเป็นของรัฐและกำลังเป็นอยู่ แบ่งปันได้อย่างอิสระ จริง ๆ แล้วทุกคนกำลังทำผิด OOP แล้วหรือค่อนข้าง OOP เป็นไปไม่ได้ถ้ากระบวนทัศน์เป็นวัตถุ ฉันใช้ OOP อย่างเคร่งครัดเพื่อแสดงว่าระบบทำงานอย่างไรและไม่เคยปฏิบัติต่อมันเหมือนจอกศักดิ์สิทธิ์หรือของแข็ง พวกเขาเป็นเพียงเครื่องมือที่ฉันใช้เป็นส่วนใหญ่ในการกำหนดแนวความคิดที่ดีพอสมควร (ผ่านการนำไปใช้) ของแนวคิดของฉัน มีคนอ่านว่านี่ถูกไหม?
coolpasta

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

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

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

คำตอบ:


22

คุณไม่สามารถเขียนรหัสที่ดีได้หากไม่มีผู้เรียก

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

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

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

True OOP ไม่ใช่สิ่งที่คุณสามารถแพร่กระจายได้ทุกที่ ใช้งานได้ภายในขอบเขตเหล่านั้นเท่านั้น

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

Michael Fetters เรียกรหัสนี้ว่าFasciaหลังจากเนื้อเยื่อเกี่ยวพันสีขาวที่ยึดส่วนสีส้มเข้าด้วยกัน

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

อุดมคติทำงานเฉพาะในสถานที่ที่เหมาะสม อย่ายอมแพ้กับพวกเขาเพียงเพราะพวกเขาไม่ได้ทำงานทุกที่ ใช้พวกเขาในที่ทำงาน สถานที่นั้นเป็นส่วนที่ชุ่มฉ่ำที่ Fascia ปกป้อง

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

ตอนนี้ถ้าคุณรู้คุณสามารถย้ายพฤติกรรมนั้นและหลีกเลี่ยงการเคลื่อนไหว เมื่อคุณรู้คุณควร คุณแค่ไม่รู้เสมอไป

บางคนเรียกสิ่งนี้ว่า และมันคือ. แต่เป็นเรื่องดีที่รู้ว่าทำไมเราต้องปฏิบัติ


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

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

ดังนั้นความแตกต่างระหว่างสิ่งนั้นและเพียงแค่วาง getters ในทุกสิ่งใน Java หรือ C #? ขออภัย แต่มันมีความหมาย การประชุมที่เน้นการใช้ Pythons ส่งสัญญาณชัดเจนว่าคุณกำลังเดินไปมาด้านหลังพนักงานเพียงประตูเดียว ตบ getters ทุกอย่างและคุณปล่อยสัญญาณนั้น ด้วยการไตร่ตรองคุณสามารถถอดส่วนบุคคลออกไปได้และยังไม่สูญเสียความหมายของสัญญาณ มันไม่มีข้อโต้แย้งเชิงโครงสร้างที่จะทำที่นี่

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

การแยกนี้ทำให้เกิดคำศัพท์สองสามคำ Data Transfer Object หรือ DTO ไม่มีพฤติกรรมใด ๆ วิธีการเพียงอย่างเดียวคือ getters และบางครั้ง setters บางครั้งตัวสร้าง ชื่อนี้โชคไม่ดีเพราะมันไม่ใช่วัตถุจริงเลย getters และ setters เป็นเพียงแค่การแก้ไขจุดบกพร่องของรหัสที่ให้ที่สำหรับวางเบรกพอยต์ หากไม่ใช่เพราะความต้องการนั้นพวกเขาก็จะกลายเป็นกองสาธารณะ ใน C ++ เราเคยเรียกมันว่า structs ความแตกต่างเพียงอย่างเดียวที่พวกเขามีจากคลาส C ++ คือค่าเริ่มต้นเป็นแบบสาธารณะ

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

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

ในท้ายที่สุดมันก็ไม่ได้มีอยู่ของผู้ได้รับที่ผิด พวกเขาดีกว่าทุ่งสาธารณะ สิ่งที่ไม่ดีคือเมื่อพวกเขาคุ้นเคยว่าคุณเป็น Object Oriented เมื่อคุณไม่ได้ Getters เป็นสิ่งที่ดี การเป็น Object Oriented นั้นดี Getters ไม่ใช่ Object Oriented ใช้ getters เพื่อแกะสลักสถานที่ที่ปลอดภัยให้เป็น Object Oriented


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

1
@coolpasta คุณสามารถสร้างวัตถุเจ้าของข้อมูล วัตถุเจ้าของข้อมูลเป็นวัตถุข้อมูล แต่ได้รับการออกแบบและตั้งชื่อเพื่อให้เจ้าของ (ชัดเจน) เป็นเจ้าของข้อมูล แน่นอนว่าวัตถุเหล่านี้จะมีผู้ได้รับ

1
@rwong คุณหมายถึงclearlyอะไร? ถ้าข้อมูลถูกส่งถึงฉันจากบางสิ่งบางอย่างโดยค่าชัดเจนตอนนี้วัตถุของฉันเป็นเจ้าของข้อมูล แต่โดยความหมายมันยังไม่ถึง ณ จุดนี้มันก็รับข้อมูลจากคนอื่น จากนั้นจะต้องทำการดำเนินการเพื่อแปลงข้อมูลทำให้เป็นของตัวเองในขณะที่ข้อมูลถูกสัมผัสคุณต้องทำการเปลี่ยนแปลงทางความหมาย: คุณได้รับข้อมูลชื่อเป็น$data_from_requestและตอนนี้คุณดำเนินการกับมันหรือไม่ $changed_dataชื่อมัน คุณต้องการเปิดเผยข้อมูลนี้ให้ผู้อื่นหรือไม่ สร้างผู้ทะเยอทะยานgetChangedRequestDataจากนั้นตั้งค่าความเป็นเจ้าของไว้อย่างชัดเจน หรือมันคืออะไร?
coolpasta

1
"คุณไม่สามารถเขียนรหัสที่ดีได้หากไม่มีตัวเรียกใช้" นี่เป็นสิ่งที่ผิดอย่างสิ้นเชิงเช่นเดียวกับความคิดที่ว่าเขตแดนต้องการผู้ได้รับ นี่ไม่ใช่การปฏิบัตินิยมเพียงความเกียจคร้าน มันค่อนข้างบิตง่ายขึ้นเพียงแค่โยนข้อมูลข้ามรั้วและจะทำกับมันความคิดเกี่ยวกับกรณีการใช้งานและการออกแบบ API พฤติกรรมที่ดีเป็นเรื่องยาก
Robert Bräutigam

2
คำตอบที่ยอดเยี่ยม!
cmaster - คืนสถานะโมนิ

10

Getters ละเมิดหลักการ Hollywood ("อย่าโทรหาเราเราจะโทรหาคุณ")

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

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

การใช้ตัวรับหมายความว่าคุณต้องการค่านั้นเมื่อคุณไม่ต้องการ

คุณอาจต้องปรับปรุงประสิทธิภาพนั้นจริง ๆ

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


1
ฉันเข้าใจและฉันจะบินไปกับความจริงของคุณที่ฉันไม่จำเป็นต้องรู้ . แต่ฉันมาถึงจุดที่ฉันต้องการ ฉันมีGeneratorวัตถุที่ลูปผ่านItemsวัตถุทั้งหมดของฉันจากนั้นโทรgetNameจากกันItemเพื่อทำบางสิ่งเพิ่มเติม มีปัญหาอะไรกับเรื่องนี้? จากนั้นในทางกลับกันGeneratorคายสตริงที่จัดรูปแบบ นี่คือภายในกรอบงานของฉันซึ่งฉันมี API อยู่ด้านบนของคนที่สามารถใช้เพื่อเรียกใช้สิ่งที่ผู้ใช้ให้ แต่ไม่ต้องสัมผัสกรอบ
coolpasta

3
What is the issue with this?ไม่มีเลยที่ฉันเห็น นั่นคือสิ่งที่mapฟังก์ชั่นทำ แต่นั่นไม่ใช่คำถามที่คุณถาม คุณถามเป็นหลัก "มีเงื่อนไขใด ๆที่ผู้ทะเยอทะยานอาจไม่เหมาะสม" ฉันตอบด้วยสองข้อ แต่นั่นไม่ได้หมายความว่าคุณละทิ้ง setters ไปโดยสิ้นเชิง
Robert Harvey

1
คุณกำลังถามคำถามผิดคนนั้น ฉันเป็นนักปฏิบัติ ฉันทำสิ่งที่เหมาะสมที่สุดกับโปรแกรมเฉพาะของฉันและไม่เก็บสต๊อกไว้ใน "หลักการ" เว้นแต่พวกเขาจะทำหน้าที่ตามวัตถุประสงค์ของฉัน
Robert Harvey

2
@ jpmc26: กรอบงาน ไม่ใช่ห้องสมุด
Robert Harvey

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

2

Getters Leak Implementation Details และ Break Abstraction

พิจารณา public int millisecondsSince1970()

ลูกค้าของคุณจะคิดว่า "โอ้นี่คือ int" และจะมีการเรียก gazillion สมมติว่ามันเป็นintทำคณิตศาสตร์จำนวนเต็มเปรียบเทียบวันที่ ฯลฯ ... เมื่อคุณตระหนักว่าคุณต้องใช้เวลานานจะมีจำนวนมาก ของรหัสดั้งเดิมที่มีปัญหา ในโลกของ Java คุณจะต้องเพิ่ม@deprecatedAPI ทุกคนจะไม่สนใจมันและคุณก็ยังคงรักษาโค้ดล้าสมัยและบั๊กกี้ไว้ :-( (ฉันถือว่าภาษาอื่นคล้ายกัน!)

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


สิ่งนี้เป็นจริง แต่โซลูชันรูปแบบคืออะไรในการบังคับใช้ชนิดข้อมูล / โครงสร้างให้กับตัวแปรของวัตถุรวมถึงสิ่งใดก็ตามที่ใช้ getter สำหรับตัวแปรนั้น
coolpasta

1
ตัวอย่างนี้แสดงให้เห็นถึงปรากฏการณ์ที่แตกต่างครอบงำจิตใจดั้งเดิม ทุกวันนี้ห้องสมุดและกรอบงานส่วนใหญ่มีคลาสที่เป็นตัวแทนtimepointและtimespanตามลำดับ ( epochเป็นค่าเฉพาะของtimepoint.) เมื่อวันที่ชั้นเรียนเหล่านี้พวกเขาให้ getters เช่นgetIntหรือหรือgetLong getDoubleหากคลาสที่จัดการเวลาถูกเขียนเพื่อให้พวกเขา (1) มอบหมายเวลาที่เกี่ยวข้องกับ arithmetics ให้กับวัตถุและวิธีการเหล่านี้และ (2) ให้การเข้าถึงวัตถุเวลาเหล่านี้ผ่านทาง getters ดังนั้นปัญหาจะได้รับการแก้ไขโดยไม่ต้องกำจัด getters .

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

1
"millisecondsSince1970" หยุดทำงาน 32 บิต int ก่อนสิ้นเดือนมกราคม 2513 ดังนั้นฉันสงสัยว่าจะมีรหัสจำนวนมากที่ใช้มัน :-)
gnasher729

1
@rwong ค่อนข้างถูกต้อง แต่คุณคิดว่าไลบรารี่ที่ต้องการซึ่งเป็นตัวแทนของ timepoints นั้นเสถียร ซึ่งพวกเขาไม่ได้ เช่นmoment.js
949300

1

ฉันคิดว่ากุญแจดอกแรกคือการจำไว้ว่าสัมบูรณ์นั้นผิดเสมอ

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

จะมีPersonคลาสที่เก็บข้อมูลจริง: ชื่อที่อยู่หมายเลขโทรศัพท์ ฯลฯAddressและPhoneเป็นคลาสด้วยดังนั้นเราจึงสามารถใช้ polymorphism ในภายหลังเพื่อสนับสนุนโครงร่างที่ไม่ใช่ของสหรัฐ ทั้งสามคลาสนั้นเป็นData Transfer Objectsดังนั้นพวกมันจะไม่เป็นอะไรเลยนอกจาก getters และ setters และนั่นก็สมเหตุสมผล: พวกเขาจะไม่มีตรรกะใด ๆ ในตัวพวกเขานอกเหนือจากการเอาชนะequalsและgetHashcode ; การขอให้พวกเขาให้ข้อมูลเกี่ยวกับบุคคลเต็มรูปแบบไม่สมเหตุสมผล: เป็นการนำเสนอ HTML แอป GUI ที่กำหนดเองแอปคอนโซลปลายทาง HTTP ฯลฯ หรือไม่

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

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

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

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

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

TL; DR

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


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

2
@rwong ฉันสนใจที่จะรู้ตัวอย่างบางอย่างสมมติว่าเป็นแอปพลิเคชัน "ปกติ" "องค์กร" ที่ดำเนินการโดยทีมเดียว เรามาสมมติความเรียบง่ายเพราะไม่มีบุคคลที่สามเข้ามาเกี่ยวข้อง คุณรู้ไหมว่าระบบที่มีในตัวเองเช่นปฏิทินร้านขายสัตว์เลี้ยงไม่ว่าคุณต้องการอะไร
Robert Bräutigam

1

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

สมาชิกที่ไม่แน่นอน

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

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

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

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

เพื่อใช้อุปมาที่อาจทำให้เห็นความแตกต่างได้ง่ายขึ้น

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

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

บางครั้ง (น้อยกว่า แต่ก็ยังบ่อยพอที่มันควรค่าแก่การกล่าวถึง) setters นั้นมีการออกแบบที่ค่อนข้างถูกต้อง ถ้าฉันมีออบเจ็กต์ลูกค้ามันค่อนข้างถูกต้องที่จะเปิดเผยเซทเทอร์สำหรับที่อยู่ของพวกเขา ไม่ว่าคุณจะเรียกมันว่าsetAddress(string address)หรือChangeMyAddressBecauseIMoved(string newAddress)หรือstring Address { get; set; }เป็นเรื่องของความหมาย สถานะภายในของวัตถุนั้นจำเป็นต้องเปลี่ยนแปลงและวิธีการที่เหมาะสมในการทำเช่นนั้นคือการตั้งค่าสถานะภายในของวัตถุนั้น แม้ว่าฉันต้องการบันทึกที่อยู่ทางประวัติศาสตร์ที่Customerมีอยู่ฉันสามารถใช้ตัวตั้งค่าเพื่อเปลี่ยนสถานะภายในของฉันอย่างเหมาะสมเพื่อทำเช่นนั้น ในกรณีนี้มันไม่สมเหตุสมผลสำหรับผู้Customerที่จะไม่เปลี่ยนรูปแบบและไม่มีวิธีที่ดีกว่าในการเปลี่ยนที่อยู่มากกว่าให้ผู้ตั้งค่าทำเช่นนั้น

ฉันไม่แน่ใจว่าใครเป็นคนแนะนำว่า Getters และ setters นั้นไม่ดีหรือแตกกระบวนทัศน์เชิงวัตถุ แต่ถ้าพวกเขาทำพวกเขาอาจทำเช่นนั้นเพื่อตอบสนองต่อคุณลักษณะภาษาเฉพาะของภาษาที่พวกเขาใช้งาน ฉันรู้สึกว่าสิ่งนี้เติบโตมาจากวัฒนธรรม "ทุกอย่างเป็นวัตถุ" ของ Java (และอาจขยายไปสู่ภาษาอื่น) โลก. NET ไม่มีการสนทนานี้เลย Getters และ Setters เป็นคุณสมบัติภาษาชั้นหนึ่งที่ใช้ไม่เพียง แต่คนที่เขียนแอปพลิเคชั่นในภาษาเท่านั้น แต่ยังใช้ภาษา API ด้วย

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

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


-1

อะไรคือสิ่งที่โดยไม่มีความคิดเห็นที่จะทำลายถ้าฉันใช้ getters ...

การบำรุงรักษาจะแตก

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

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

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


1
counterexample พิจารณาฟังก์ชั่นที่ตอบสนองความต้องการย้ำสองCollections (A1, B1), (A2, B2), ...พร้อมกันในขณะที่ ต้องใช้ "zip" คุณใช้งาน "zip" อย่างไรหากมีการใช้งานฟังก์ชันวนซ้ำในหนึ่งในสองCollectionวิธี - คุณจะเข้าถึงรายการที่เกี่ยวข้องในอีกรายการหนึ่งได้อย่างไร

@rwong Collectionมีการใช้zipกับตัวเองขึ้นอยู่กับวิธีเขียนไลบรารี หากไม่เป็นเช่นนั้นคุณจะนำไปใช้และส่งคำขอดึงไปยังผู้สร้างห้องสมุด โปรดทราบว่าฉันเห็นพ้องต้องกันว่าเขตแดนบางแห่งอาจต้องการผู้ให้บริการเป็นครั้งคราวปัญหาที่แท้จริงของฉันคือผู้ให้บริการอยู่ทุกที่ในปัจจุบัน
Robert Bräutigam

ในกรณีที่ว่าหมายความว่าห้องสมุดจะต้องมี getters บนวัตถุอย่างน้อยสำหรับการดำเนินงานของตัวเองCollection zip(นั่นคือผู้ทะเยอทะยานต้องมีการเปิดเผยแพ็คเกจอย่างน้อย) และตอนนี้คุณขึ้นอยู่กับผู้สร้างห้องสมุดที่ยอมรับคำขอการดึงของคุณ ...
rwong

ฉันไม่ค่อยเข้าใจคุณ Collectionชัดสามารถเข้าถึงรัฐภายในของตัวเองหรือจะแม่นยำมากขึ้นใด ๆCollectionเช่นสามารถเข้าถึงของอื่น ๆ ที่รัฐภายใน มันเป็นประเภทเดียวกันโดยไม่จำเป็นต้องได้รับ
Robert Bräutigam

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