มันเป็นไรไหมที่ชั้นเรียนจะใช้วิธีสาธารณะของตัวเอง?


23

พื้นหลัง

ขณะนี้ฉันมีสถานการณ์ที่มีวัตถุที่ส่งและรับจากอุปกรณ์ ข้อความนี้มีโครงสร้างหลายอย่างดังนี้:

public void ReverseData()
public void ScheduleTransmission()

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

สำหรับ "รับ" ฉันหมายถึงว่าReverseDataจะถูกเรียกจากภายนอกในobject_receivedตัวจัดการเหตุการณ์เพื่อยกเลิกการย้อนกลับข้อมูล

คำถาม

โดยทั่วไปจะยอมรับวัตถุที่เรียกวิธีการสาธารณะของตนเองหรือไม่?


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

@ Kaan เหล่านี้ไม่ใช่ชื่อจริงของวิธีการของฉัน แต่เกี่ยวข้องอย่างใกล้ชิด ในความเป็นจริง "reverse data" จะกลับคำเพียง 8 บิตของคำทั้งหมดและจะทำเมื่อเรารับและส่ง
Snoop

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

2
@ StevieV ฉันเชื่อว่านี่จะขยายความกังวล Kaan เพิ่มขึ้นเท่านั้น ดูเหมือนว่าคุณกำลังเปิดเผยวิธีที่เปลี่ยนสถานะของวัตถุและสถานะจะขึ้นอยู่กับว่ามีการเรียกใช้วิธีนั้นกี่ครั้ง สิ่งนี้ทำให้ฝันร้ายในการพยายามติดตามสถานะของวัตถุในโค้ดของคุณ มันฟังดูเพิ่มเติมว่าคุณจะได้รับประโยชน์จากประเภทข้อมูลแยกจากสถานะแนวคิดเหล่านี้ดังนั้นคุณสามารถบอกได้ว่ามันอยู่ในส่วนใดของรหัสที่กำหนดโดยไม่ต้องกังวลกับมัน
jpmc26

2
@StevieV คล้าย Data และ SerializedData ข้อมูลคือสิ่งที่โค้ดเห็นและ SerializedData คือสิ่งที่ถูกส่งผ่านเครือข่าย จากนั้นทำข้อมูลย้อนกลับ (หรือค่อนข้างเป็นอนุกรม / เป็นข้อมูล deserialize) แปลงจากประเภทหนึ่งไปเป็นอีกประเภทหนึ่ง
csiz

คำตอบ:


33

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

มันลงมากับการออกแบบของชั้นเรียนจริงๆ ReverseData () ดูเหมือนว่าพฤติกรรมพื้นฐานของชั้นเรียนของคุณ หากคุณต้องการใช้ลักษณะการทำงานนี้ในที่อื่นคุณอาจไม่ต้องการใช้เวอร์ชันอื่น คุณเพียงแค่ต้องระวังว่าคุณจะไม่ปล่อยให้รายละเอียดเฉพาะของ ScheduleTransmission () รั่วไหลไปยัง ReverseData () ที่จะสร้างปัญหา แต่เนื่องจากคุณใช้สิ่งนี้นอกห้องเรียนอยู่แล้วคุณอาจคิดไปแล้วว่าผ่าน


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

2
"เพื่อให้บางคนไม่แปลกใจเมื่อเอาชนะ ReverseData () เปลี่ยนวิธีการทำงานของ ScheduleTransmission ()" : ไม่ใช่ไม่ใช่ อย่างน้อยไม่ได้อยู่ใน C # (โปรดทราบว่าคำถามนั้นมีแท็ก C #) ยกเว้นว่าReverseData()เป็นนามธรรมไม่ว่าคุณจะทำอะไรในชั้นเรียนเด็กก็เป็นวิธีการดั้งเดิมที่จะถูกเรียกเสมอ
Arseni Mourzenko

2
@MainMa หรือvirtual... และถ้าคุณกำลังเอาชนะวิธีการแล้วโดยความหมายจะต้องมีหรือvirtual abstract
Pokechu22

1
@ Pokechu22: แน่นอนvirtualเกินไป ในทุกกรณีลายเซ็นอ้างอิงจาก OP คือpublic void ReverseData()ดังนั้นส่วนหนึ่งของคำตอบเกี่ยวกับการเอาชนะสิ่งต่าง ๆ นั้นทำให้เข้าใจผิดเล็กน้อย
Arseni Mourzenko

@MainMa คุณกำลังบอกว่าถ้าเมธอดคลาสฐานเรียกเมธอดเสมือนจะไม่ทำงานในลักษณะเสมือนปกติยกเว้นว่าเป็นabstract? ฉันค้นหาคำตอบเกี่ยวabstractกับ vs virtualและไม่เห็นที่กล่าวถึง: ค่อนข้างเป็นนามธรรมหมายความว่าไม่มีรุ่นใดที่ให้พื้นฐานนี้
JDługosz

20

อย่างแน่นอน

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

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

อย่างไรก็ตามคุณอาจระมัดระวังรูปแบบต่อไปนี้:

  1. วิธีการHello()เรียกWorld(string, int, int)เช่นนี้:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

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

  2. วิธีการHello(ComplexEntity)เรียกWorld(string, int, int)เช่นนี้:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

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

  3. วิธีการเพียงเรียกวิธีสาธารณะอื่น ๆ โดยไม่ต้องเพิ่มมูลค่าที่สำคัญ

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

  4. วิธีการตรวจสอบอินพุตและจากนั้นเรียกวิธีอื่น:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    แต่Worldควรตรวจสอบความถูกต้องของพารามิเตอร์เองหรือเป็นส่วนตัว


ความคิดเดียวกันนี้จะนำไปใช้หรือไม่หาก ReverseData เป็นจริงภายในและใช้ทั่วทั้งเนมสเปซที่มีอินสแตนซ์ของ "วัตถุ"
Snoop

1
@ StevieV: ใช่แน่นอน
Arseni Mourzenko

@MainMa ฉันไม่เข้าใจว่าอะไรคือสาเหตุที่อยู่เบื้องหลังคะแนน1และ2
Kapol

@Kapol: ขอบคุณสำหรับความคิดเห็นของคุณ ฉันแก้ไขคำตอบของฉันโดยเพิ่มคำอธิบายเกี่ยวกับสองประเด็นแรก
Arseni Mourzenko

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

8

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

java.util.ArrayList#ensureCapacity(int)ในห้องสมุดที่ดีที่สุดอย่างที่คุณต้องการคัดลอกสำนวนใส่ไว้ใน

EnsureCapacity

  • ensureCapacity เป็นสาธารณะ
  • ensureCapacity มีการตรวจสอบขอบเขตที่จำเป็นทั้งหมดและค่าเริ่มต้น ฯลฯ
  • ensureCapacity โทร ensureExplicitCapacity

ensureCapacityInternal

  • ensureCapacityInternal เป็นส่วนตัว
  • ensureCapacityInternal มีการตรวจสอบข้อผิดพลาดเล็กน้อยเนื่องจากอินพุตทั้งหมดมาจากภายในชั้นเรียน
  • ensureCapacityInternal ยังโทร ensureExplicitCapacity

ensureExplicitCapacity

  • ensureExplicitCapacity เป็นแบบส่วนตัว
  • ensureExplicitCapacityมีไม่มีการตรวจสอบข้อผิดพลาด
  • ensureExplicitCapacity ทำงานได้จริง
  • ensureExplicitCapacityไม่ได้ถูกเรียกจากที่ใดก็ได้ยกเว้นโดยensureCapacityและensureCapacityInternal

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

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


3

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

วิธีการสาธารณะมักจะมีสัญญา "นำวัตถุในสถานะที่สอดคล้องกันทำสิ่งที่เหมาะสมออกจากวัตถุในสถานะที่สอดคล้องกัน (อาจแตกต่างกัน)" ที่นี่ "สอดคล้อง" อาจหมายถึงการยกตัวอย่างเช่นว่าLengthของList<T>ที่มีขนาดใหญ่กว่าของไม่มีCapacityและที่อ้างอิงองค์ประกอบที่ดัชนีจาก0ไปLength-1จะไม่โยน

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


1

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

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


-5

ขออภัยที่ฉันไม่เห็นด้วยกับคำตอบ 'ใช่คุณสามารถ' ส่วนใหญ่และพูดว่า:

ฉันจะกีดกันชั้นเรียนที่เรียกวิธีการหนึ่งจากที่อื่น

มีปัญหาที่อาจเกิดขึ้นสองสามข้อในการฝึกนี้

1: การวนซ้ำไม่สิ้นสุดในคลาสที่สืบทอด

ดังนั้นคลาสพื้นฐานของคุณจึงเรียก method1 จาก method2 แต่จากนั้นคุณหรือคนอื่นสืบทอดมาจากมันและซ่อน method1 ด้วยวิธีการใหม่ซึ่งเรียก method2

2: กิจกรรมการบันทึก ฯลฯ

เช่นฉันมีวิธีการหนึ่งที่จะเพิ่มเหตุการณ์ '1 เพิ่ม!' ฉันอาจไม่ต้องการวิธี Add10 เพื่อเพิ่มเหตุการณ์นั้นเขียนลงในบันทึกหรืออะไรก็ตามสิบครั้ง

3: เธรดและการหยุดชะงักอื่น ๆ

เช่น InsertComplexData เปิดการเชื่อมต่อฐานข้อมูลเริ่มทำธุรกรรมล็อคตารางจากนั้นเรียกใช้ InsertSimpleData พร้อมเปิดการเชื่อมต่อเริ่มทำธุรกรรมรอให้ปลดล็อคตาราง ...

ฉันแน่ใจว่ามีเหตุผลมากกว่านี้อีกคำตอบหนึ่งที่สัมผัสกับ 'คุณแก้ไข method1 และประหลาดใจที่ method2 เริ่มทำงานต่างกัน'

Generaly หากคุณมีสองวิธีสาธารณะซึ่งใช้รหัสร่วมกันดีกว่าที่จะทำให้ทั้งคู่เรียกใช้วิธีส่วนตัวแทนการเรียกอีกวิธีหนึ่ง

แก้ไข ----

ให้ขยายในกรณีเฉพาะใน OP

เราไม่มีรายละเอียดมากมาย แต่เรารู้ว่า ReverseData นั้นถูกเรียกใช้โดยตัวจัดการเหตุการณ์บางประเภทเช่นเดียวกับวิธีการถ่ายโอนกำหนดการ

ฉันคิดว่าข้อมูลย้อนกลับยังเปลี่ยนสถานะภายในของวัตถุ

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

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

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

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

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

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


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

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

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

thibg เพียง 'รหัส' ในตัวอย่างหุ้นของฉันเป็นวิธีสาธารณะหนึ่งเรียกอีกวิธีหนึ่ง หากคุณคิดว่า 'โค้ดไม่ดี' นั้นดี! นั่นคือการต่อสู้ของฉัน
Ewan

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