สไตล์การเข้ารหัส OOP: เริ่มต้นทุกอย่างบนตัวสร้างหรือไม่


14

ฉันยังคิดว่าตัวเองเป็นโปรแกรมเมอร์ฝึกงานดังนั้นฉันมักจะมองหาวิธีที่ "ดีกว่า" สำหรับการเขียนโปรแกรมทั่วไป วันนี้เพื่อนร่วมงานของฉันแย้งว่าสไตล์การเขียนรหัสของฉันทำงานได้โดยไม่จำเป็นและฉันต้องการฟังความคิดเห็นจากผู้อื่น โดยทั่วไปเมื่อฉันออกแบบคลาสในภาษา OOP (โดยทั่วไปคือ C ++ หรือ Python) ฉันจะแยกการกำหนดค่าเริ่มต้นออกเป็นสองส่วน:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(หรือเทียบเท่าหลาม)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

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


1
เกี่ยวข้อง: stackoverflow.com/questions/3127454/…
Doc Brown

2
ดูเพิ่มเติมได้ที่: stackoverflow.com/questions/33124542/…
Doc Brown

1
และสำหรับ Python: stackoverflow.com/questions/20661448/…
Doc Brown

ทำไมคุณถึงทำอย่างนั้น? นิสัย? คุณเคยให้เหตุผลที่จะทำมันหรือไม่?
JeffO

@JeffO ฉันมีนิสัยนี้เมื่อฉันทำงานเพื่อสร้าง GUI ด้วยไลบรารี MFC คลาสที่เกี่ยวข้องกับ UI ส่วนใหญ่เช่น CApp, CWindow, CDlg และอื่น ๆ มีฟังก์ชั่น OnInit () ที่คุณสามารถเขียนทับด้วยซึ่งตอบสนองต่อข้อความที่ไม่เหมาะสม
Caladbolgll

คำตอบ:


28

แม้ว่าบางครั้งมันจะเป็นปัญหา แต่ก็มีข้อดีหลายประการในการเริ่มต้นทุกอย่างในตัวสร้าง:

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

2

คิดถึงสิ่งที่เป็นนามธรรมที่คุณให้แก่ผู้ใช้ของคุณ

ทำไมต้องแยกบางสิ่งที่สามารถทำได้ในหนึ่งนัดเป็นสองนัด?

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

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


1

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


0

มีหลายกรณีที่วัตถุนั้นมีการเริ่มต้นจำนวนมากซึ่งสามารถแบ่งออกเป็นสองประเภท:

  1. คุณสมบัติที่ไม่เปลี่ยนรูปหรือไม่จำเป็นต้องตั้งค่าใหม่

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

ที่นี่ส่วนที่สองของการเริ่มต้นถูกเก็บไว้ในฟังก์ชั่นแยกกล่าวว่า InitialiseObject () สามารถเรียกได้ใน ctor

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


0

อย่างที่คนอื่น ๆ พูดกันโดยทั่วไปแล้วมันเป็นความคิดที่ดีที่จะเริ่มต้นในตัวสร้าง

อย่างไรก็ตามมีเหตุผลที่ไม่เกี่ยวกับเรื่องนั้นที่อาจจะใช่หรือไม่ใช่ในบางกรณี

การจัดการข้อผิดพลาด

ในหลายภาษาวิธีเดียวที่จะส่งสัญญาณข้อผิดพลาดในตัวสร้างคือการยกข้อยกเว้น

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

อาจเป็นตัวอย่างที่พบบ่อยที่สุดของนี้อยู่ใน C ++ ถ้ามาตรฐานโครงการ / องค์กรคือการปิดข้อยกเว้น

เครื่องรัฐ

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

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

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

ข้อเสียอย่างที่คนอื่น ๆ พูดกันคือตอนนี้คุณมีภาระในการจัดการของรัฐกับผู้ใช้งานในชั้นเรียนของคุณ ถ้าคุณสามารถจัดการกับการก่อสร้างได้คุณก็สามารถพูดได้ใช้ RAII เพื่อทำสิ่งนี้โดยอัตโนมัติ

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