คุณควรใช้ตัวแปรสมาชิกที่มีการป้องกันหรือไม่?


98

คุณควรใช้ตัวแปรสมาชิกที่มีการป้องกันหรือไม่? ข้อดีคืออะไรและปัญหานี้ทำให้เกิดอะไรได้บ้าง?

คำตอบ:


75

คุณควรใช้ตัวแปรสมาชิกที่มีการป้องกันหรือไม่?

ขึ้นอยู่กับว่าคุณจู้จี้จุกจิกแค่ไหนในการซ่อนสถานะ

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

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


1
คุณสามารถแสดงความคิดเห็นเกี่ยวกับประสิทธิภาพของตัวแปรที่มีการป้องกันเทียบกับตัวแปรส่วนตัวด้วยวิธี get / set ได้หรือไม่?
Jake

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

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

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

2
@Black Clearly Allain หมายถึง 'พวกเขาไม่สามารถเข้าถึง ' สมาชิกเหล่านั้นได้ดังนั้นจึงไม่สามารถสร้างโค้ดต่อต้านพวกเขาได้ทำให้ผู้เขียนของคลาสนั้นมีอิสระที่จะลบ / เปลี่ยนแปลงสมาชิกที่ได้รับการป้องกันในภายหลัง (แน่นอนสำนวน pimpl จะช่วยให้สามารถซ่อนพวกมันได้ด้วยสายตาและจากหน่วยการแปลรวมถึงส่วนหัวด้วย)
underscore_d

32

โดยทั่วไปหากมีบางสิ่งที่ไม่ได้จงใจคิดว่าเป็นสาธารณะฉันจะทำให้เป็นแบบส่วนตัว

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

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

ฉันว่านี่เป็นวิธีที่ดี (และอาจเป็นวิธีที่ดีที่สุดในการดำเนินการนี้) สำหรับนักพัฒนาซอฟต์แวร์ส่วนใหญ่

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

ข้อยกเว้นที่แท้จริงเพียงประการเดียวคือหากคุณอยู่ในธุรกิจการจัดส่ง binary dll ในรูปแบบ black-box ไปยังบุคคลที่สาม สิ่งนี้ประกอบด้วย Microsoft ผู้จำหน่าย 'Custom DataGrid Control' เหล่านั้นและอาจมีแอพขนาดใหญ่อื่น ๆ อีกสองสามตัวที่มาพร้อมกับไลบรารีความสามารถในการขยาย ถ้าคุณไม่อยู่ในหมวดหมู่นั้นก็ไม่คุ้มที่จะเสียเวลา / ความพยายามมากังวลกับเรื่องแบบนี้


31

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

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


2
ไม่ควรno particular advantage over protected methods/propertiesจะเป็นno particular advantage over *private* methods/properties?
Penghe Geng

ไม่เพราะฉัน / กำลังพูดถึงข้อดี / ข้อเสียของวิธีต่างๆในการสื่อสารระหว่างคลาสที่ได้รับและฐานของพวกเขา - เทคนิคทั้งหมดนี้จะได้รับการ 'ป้องกัน' - ความแตกต่างคือไม่ว่าจะเป็นตัวแปรสมาชิก (ฟิลด์) หรือคุณสมบัติ / วิธีการ ( คือรูทีนย่อยบางชนิด)
Will Dean

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

10
สิ่งที่น่าทึ่งกว่านั้นคือฉันเห็นด้วยกับตัวเองตลอดช่วงเวลานั้น ...
Will Dean

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

9

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

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

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

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

ตัวอย่างเช่น:

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

ด้วยการ จำกัด ตัวแปรให้เป็นส่วนตัวฉันจึงสามารถบังคับใช้พฤติกรรมที่ฉันต้องการผ่าน setters หรือ getters


8

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

นี่คือสิ่งที่ superclass ของคุณไม่สามารถทำได้หลังจากสร้างตัวแปรสมาชิกที่ได้รับการป้องกันแทนที่จะเป็น private-with-accessors:

  1. เกียจคร้านสร้างมูลค่าทันทีเมื่อกำลังอ่านคุณสมบัติ หากคุณเพิ่มเมธอด getter ที่ได้รับการป้องกันคุณสามารถสร้างมูลค่าและส่งคืนได้อย่างเกียจคร้าน

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

  3. ตั้งค่าเบรกพอยต์หรือเพิ่มเอาต์พุตการดีบักเมื่อมีการอ่านหรือเขียนตัวแปร

  4. เปลี่ยนชื่อตัวแปรสมาชิกโดยไม่ต้องค้นหารหัสทั้งหมดที่อาจใช้

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


7

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

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

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

วิธีการของ Accessor / mutator ช่วยเพิ่มความเสถียรของ API และความยืดหยุ่นในการใช้งานภายใต้การบำรุงรักษา

นอกจากนี้หากคุณเป็นคนเจ้าระเบียบ OO วัตถุจะทำงานร่วมกัน / สื่อสารโดยการส่งข้อความไม่ใช่สถานะการอ่าน / การตั้งค่า

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


4

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

แต่ฉันมีตัวอย่างที่ดีอย่างหนึ่ง: สมมติว่าคุณสามารถใช้คอนเทนเนอร์ทั่วไปได้ มีการใช้งานภายในและตัวเข้าถึงภายใน แต่คุณต้องเสนอการเข้าถึงข้อมูลแบบสาธารณะอย่างน้อย 3 รายการ ได้แก่ map, hash_map, vector-like จากนั้นคุณมีสิ่งที่ต้องการ:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

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

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


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

@gbjbaanb: คุณกำลังขัดแย้งกับตัวเอง "มันไม่ได้ทำลายการห่อหุ้มอีกต่อไปกว่าที่สมาชิกสาธารณะทำ" แตกต่างจากคลาสที่ได้รับ "[เท่านั้น] ที่สามารถใช้คลาส 'สถานะ" ได้ "ได้รับการคุ้มครอง" เป็นสื่อกลางระหว่างภาครัฐและเอกชน ดังนั้น "การป้องกัน [... ] แตกค่อนข้างห่อหุ้ม" ยังคงเป็นจริง ...
paercebal

ที่จริงแล้วในภาษา c ++ คอนเทนเนอร์อะแด็ปเตอร์เช่น std :: stack จะแสดงอ็อบเจ็กต์คอนเทนเนอร์ที่อยู่ภายใต้ตัวแปรที่มีการป้องกันที่เรียกว่า "c"
Johannes Schaub - litb

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

3

ในระยะสั้นใช่

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


1
ในทางกลับกันตัวแปรสมาชิกส่วนตัวก็ไม่จำเป็นเช่นกัน สาธารณะเพียงพอสำหรับการใช้งานใด ๆ
Alice

3

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


2

สำหรับข้อมูลโดยละเอียดเกี่ยวกับตัวปรับการเข้าถึง. Net ไปที่นี่

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


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