Kiểu mã hóa OOP: khởi tạo mọi thứ trên constructor?


14

Tôi vẫn coi mình là một lập trình viên tập sự, vì vậy tôi luôn mong muốn học một cách "tốt hơn" cho lập trình thông thường. Hôm nay, đồng nghiệp của tôi đã lập luận rằng phong cách mã hóa của tôi thực hiện một số công việc không cần thiết và tôi muốn nghe ý kiến ​​từ những người khác. Thông thường, khi tôi thiết kế một lớp bằng ngôn ngữ OOP (Thường là C ++ hoặc Python), tôi sẽ tách phần khởi tạo thành hai phần khác nhau:

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)
    }
}

(hoặc, tương đương trăn)

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!"

Ý kiến ​​của bạn về phương pháp này là gì? Tôi có nên hạn chế tách quá trình khởi tạo? Câu hỏi không chỉ giới hạn ở C ++ và Python và câu trả lời cho các ngôn ngữ khác cũng được đánh giá cao.





Tại sao bạn thường làm điều đó? Thói quen? Bạn đã bao giờ đưa ra một lý do để làm điều đó?
JeffO 8/12/2016

@JeffO Tôi có thói quen này khi làm việc để tạo GUI với thư viện MFC. Hầu hết các lớp liên quan đến giao diện người dùng, chẳng hạn như CApp, CWindow, CDlg, v.v., có các hàm OnInit () mà bạn có thể ghi đè lên, đáp ứng với các thông điệp trùng khớp của chúng.
Caladbolgll

Câu trả lời:


28

Mặc dù đôi khi nó có vấn đề, nhưng có nhiều lợi thế để khởi tạo mọi thứ trong hàm tạo:

  1. Nếu có một lỗi xảy ra, nó sẽ xảy ra càng nhanh càng tốt và dễ chẩn đoán nhất. Ví dụ: nếu null là một giá trị đối số không hợp lệ, kiểm tra và thất bại trong hàm tạo.
  2. Đối tượng luôn ở trạng thái hợp lệ. Một đồng nghiệp không thể phạm sai lầm và quên gọi initMyClass1()nó không ở đó . "Các thành phần rẻ nhất, nhanh nhất và đáng tin cậy nhất là những thành phần không có ở đó."
  3. Nếu nó có ý nghĩa, đối tượng có thể được làm cho bất biến có rất nhiều lợi thế.

2

Hãy suy nghĩ về sự trừu tượng mà bạn đang cung cấp cho người dùng của mình.

Tại sao lại chia một cái gì đó có thể được thực hiện trong một lần bắn thành hai?

Việc khởi tạo thêm chỉ là thứ gì đó bổ sung cho các lập trình viên sử dụng API của bạn để ghi nhớ và cung cấp thêm lỗi nếu họ không làm đúng, nhưng giá trị nào đối với họ cho gánh nặng thêm này?

Bạn muốn cung cấp chết đơn giản, dễ sử dụng, khó đi trừu tượng. Lập trình là đủ khó mà không có những thứ vô cớ để nhớ / hoops để nhảy qua. Bạn muốn người dùng API của mình (ngay cả khi bạn chỉ sử dụng API của riêng mình) rơi vào hố thành công .


1

Khởi tạo mọi thứ trừ khu vực dữ liệu lớn. Các công cụ phân tích tĩnh sẽ gắn cờ các trường không được khởi tạo trong hàm tạo. Tuy nhiên, cách hiệu quả / an toàn nhất là có tất cả các biến thành viên với các hàm tạo mặc định và chỉ khởi tạo rõ ràng những biến yêu cầu khởi tạo không mặc định.


0

Có những trường hợp đối tượng có nhiều khởi tạo có thể được chia thành hai loại:

  1. Các thuộc tính không thay đổi hoặc không cần thiết lập lại.

  2. Các thuộc tính có thể cần hoàn nguyên về các giá trị ban đầu (hoặc các giá trị templatised) dựa trên một số điều kiện sau khi hoàn thành công việc của chúng, loại thiết lập lại mềm. ví dụ: các kết nối trong nhóm kết nối.

Ở đây, phần thứ hai của việc khởi tạo được giữ trong một hàm riêng biệt, có thể được gọi là InitialiseObject (), trong ctor.

Chức năng tương tự có thể được gọi sau nếu yêu cầu thiết lập lại mềm, mà không phải loại bỏ và tạo lại đối tượng.


0

Như những người khác đã nói, nói chung nên khởi tạo trong hàm tạo.

Tuy nhiên, có những lý do không có thể có hoặc không áp dụng trong các trường hợp cụ thể.

Xử lý lỗi

Trong rất nhiều ngôn ngữ, cách duy nhất để báo hiệu lỗi trong hàm tạo là đưa ra một ngoại lệ.

Nếu việc khởi tạo của bạn có cơ hội phát sinh lỗi hợp lý, ví dụ như liên quan đến IO hoặc các tham số của nó có thể là đầu vào của người dùng, thì cơ chế duy nhất mở ra cho bạn là đưa ra một ngoại lệ. Trong một số trường hợp, đây có thể không phải là điều bạn muốn và có thể có ý nghĩa hơn để tách mã dễ bị lỗi thành một hàm khởi tạo riêng.

Có lẽ ví dụ phổ biến nhất về điều này là trong C ++ nếu tiêu chuẩn dự án / tổ chức là tắt ngoại lệ.

Máy nhà nước

Đây là trường hợp bạn đang mô hình hóa một đối tượng có chuyển trạng thái rõ ràng. Ví dụ, một tập tin hoặc một ổ cắm có thể được mở và đóng.

Trong trường hợp này, thông thường việc xây dựng đối tượng (và xóa) chỉ xử lý các thuộc tính hướng bộ nhớ (tên tệp, cổng, v.v.). Sau đó sẽ có các chức năng để quản lý cụ thể các chuyển đổi trạng thái, ví dụ như mở, đóng, đó là các chức năng khởi tạo và phá bỏ hiệu quả.

Những ưu điểm là trong xử lý lỗi, như trên nhưng cũng có thể có trường hợp tách cấu trúc khỏi khởi tạo (giả sử bạn xây dựng một vectơ tệp và mở chúng không đồng bộ).

Nhược điểm, như những người khác đã nói, là bây giờ bạn đặt gánh nặng quản lý nhà nước lên người dùng các lớp học của bạn. Nếu bạn có thể quản lý chỉ với việc xây dựng thì bạn có thể nói, hãy sử dụng RAII để thực hiện việc này một cách tự động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.