Tại sao tôi nên sử dụng các phương thức khởi tạo và dọn dẹp riêng biệt thay vì đưa logic vào hàm tạo và hàm hủy cho các thành phần động cơ?


9

Tôi đang làm việc trên công cụ trò chơi của riêng mình và tôi hiện đang thiết kế các trình quản lý của mình. Tôi đã đọc rằng để quản lý bộ nhớ, sử dụng Init()và các CleanUp()chức năng sẽ tốt hơn sau đó sử dụng các hàm tạo và hàm hủy.

Tôi đã tìm kiếm các ví dụ mã C ++, để xem các chức năng đó hoạt động như thế nào và làm thế nào tôi có thể triển khai chúng vào công cụ của mình. Làm thế nào Init()CleanUp()hoạt động, và làm thế nào tôi có thể thực hiện chúng vào động cơ của tôi?



Đối với C ++, hãy xem stackoverflow.com/questions/3786853/ Từ Lý do chính để sử dụng init () là 1) Ngăn chặn ngoại lệ và sự cố trong hàm tạo với các hàm trợ giúp 2) Có thể sử dụng các phương thức ảo từ lớp phụ thuộc vòng 3 4) làm phương thức riêng tư để tránh sao chép mã
brita_

Câu trả lời:


12

Thật ra nó khá đơn giản:

Thay vì có một Trình xây dựng thực hiện cài đặt của bạn,

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... yêu cầu nhà xây dựng của bạn làm ít hoặc không làm gì cả, và viết một phương thức được gọi .inithoặc .initialize, phương thức này sẽ làm những gì nhà xây dựng của bạn thường làm.

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

Vì vậy, bây giờ thay vì chỉ đi như:

Thing thing = new Thing(1, 2, 3, 4);

Bạn có thể đi:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

Lợi ích ở đây là giờ đây bạn có thể sử dụng phép tiêm phụ thuộc / đảo ngược kiểm soát dễ dàng hơn trong các hệ thống của mình.

Thay vì nói

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

Bạn có thể xây dựng người lính, cho anh ta một phương pháp trang bị, nơi bạn đưa cho anh ta một vũ khí và THEN gọi tất cả các chức năng còn lại của hàm tạo.

Vì vậy, bây giờ, thay vì phân loại kẻ thù trong đó một người lính có súng lục và người khác có súng trường và người khác có súng ngắn, và đó là điểm khác biệt duy nhất, bạn chỉ có thể nói:

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

Cùng đối phó với sự hủy diệt. Nếu bạn có nhu cầu đặc biệt (loại bỏ trình lắng nghe sự kiện, loại bỏ các trường hợp khỏi mảng / bất kỳ cấu trúc nào bạn đang làm việc, v.v.), thì bạn sẽ gọi chúng theo cách thủ công để bạn biết chính xác thời điểm và địa điểm trong chương trình đang diễn ra.

BIÊN TẬP


Như Kryotan đã chỉ ra, bên dưới, câu trả lời này là "Làm thế nào" của bài viết gốc , nhưng không thực sự làm tốt công việc "Tại sao".

Như bạn có thể thấy trong câu trả lời ở trên, có thể không có nhiều sự khác biệt giữa:

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

và viết

var myObj = new Object(1,2);

trong khi chỉ có một hàm xây dựng lớn hơn.
Có một đối số được đưa ra cho các đối tượng có 15 hoặc 20 điều kiện trước, điều này sẽ khiến cho một nhà xây dựng rất, rất khó để làm việc và nó sẽ làm cho mọi thứ dễ nhìn và dễ nhớ hơn, bằng cách kéo những thứ đó ra ngoài giao diện , để bạn có thể thấy cách khởi tạo hoạt động, cao hơn một cấp.

Cấu hình tùy chọn của các đối tượng là một phần mở rộng tự nhiên cho điều này; tùy chọn thiết lập các giá trị trên giao diện, trước khi làm cho đối tượng chạy.
JS có một số phím tắt tuyệt vời cho ý tưởng này, dường như không phù hợp với các ngôn ngữ giống như c được gõ mạnh hơn.

Điều đó nói rằng, rất có thể, nếu bạn đang xử lý một danh sách đối số dài trong hàm tạo của bạn, thì đối tượng của bạn quá lớn và quá nhiều, như vậy. Một lần nữa, đây là một sở thích cá nhân và có những trường hợp ngoại lệ xa và rộng, nhưng nếu bạn chuyển 20 thứ vào một đối tượng, rất có thể bạn có thể tìm ra cách làm cho đối tượng đó làm ít hơn, bằng cách tạo ra các đối tượng nhỏ hơn .

Một lý do thích hợp hơn và một lý do có thể áp dụng rộng rãi là việc khởi tạo một đối tượng phụ thuộc vào dữ liệu không đồng bộ mà hiện tại bạn không có.

Bạn biết rằng bạn cần đối tượng, vì vậy dù sao bạn cũng sẽ tạo ra nó, nhưng để nó hoạt động chính xác, nó cần dữ liệu từ máy chủ hoặc từ một tệp khác mà bây giờ nó cần tải.

Một lần nữa, cho dù bạn chuyển dữ liệu cần thiết vào một init khổng lồ hay xây dựng giao diện không thực sự quan trọng đối với khái niệm này, cũng quan trọng đối với giao diện của đối tượng và thiết kế hệ thống của bạn ...

Nhưng về mặt xây dựng đối tượng, bạn có thể làm một cái gì đó như thế này:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader có thể được thông qua tên tệp, hoặc tên tài nguyên hoặc bất cứ thứ gì, tải tài nguyên đó - có thể nó tải các tệp âm thanh hoặc dữ liệu hình ảnh hoặc có thể tải các số liệu thống kê ký tự đã lưu ...

... và sau đó nó sẽ đưa dữ liệu đó trở lại obj_w_async_dependencies.init(result); .

Loại động này được tìm thấy thường xuyên trong các ứng dụng web.
Không nhất thiết phải trong quá trình xây dựng của một đối tượng, đối với các ứng dụng cấp cao hơn: ví dụ: các phòng trưng bày có thể tải và khởi tạo ngay lập tức, sau đó hiển thị ảnh khi chúng truyền vào - đó không thực sự là khởi tạo không đồng bộ, nhưng ở đó thường thấy hơn trong các thư viện JavaScript.

Một mô-đun có thể phụ thuộc vào một mô-đun khác, và vì vậy việc khởi tạo mô-đun đó có thể được hoãn lại cho đến khi quá trình tải các phụ thuộc hoàn tất.

Về các trường hợp cụ thể của trò chơi này, hãy xem xét một Gamelớp thực tế .

Tại sao chúng ta không thể gọi .starthoặc .runtrong nhà xây dựng?
Tài nguyên cần phải được tải - phần còn lại của mọi thứ đã được xác định khá nhiều và rất tốt để đi, nhưng nếu chúng ta thử chạy trò chơi mà không có kết nối cơ sở dữ liệu, hoặc không có kết cấu hoặc mô hình hoặc âm thanh hoặc cấp độ, thì sẽ không được một trò chơi đặc biệt thú vị ...

... vậy thì, sự khác biệt giữa những gì chúng ta thấy của một điển hình Game, ngoại trừ việc chúng ta đặt cho phương thức "đi trước" của nó một cái tên thú vị hơn .init(hoặc ngược lại, phá vỡ sự khởi tạo cách xa nhau hơn, để tách riêng tải, thiết lập những thứ đã được tải và chạy chương trình khi mọi thứ đã được thiết lập).


2
" Sau đó, bạn sẽ gọi chúng theo cách thủ công, để bạn biết chính xác thời gian và địa điểm trong chương trình đang diễn ra. " Lần duy nhất trong C ++, nơi một hàm hủy sẽ được gọi ngầm là cho một đối tượng ngăn xếp (hoặc toàn cầu). Đối tượng phân bổ heap yêu cầu phá hủy rõ ràng. Vì vậy, nó luôn luôn rõ ràng khi đối tượng đang được giải quyết.
Nicol Bolas

6
Thật không chính xác khi nói rằng bạn cần phương pháp riêng biệt này để cho phép tiêm các loại vũ khí khác nhau, hoặc đây là cách duy nhất để tránh sự phổ biến của các lớp con. Bạn có thể vượt qua các trường hợp vũ khí thông qua các nhà xây dựng! Vì vậy, đó là -1 đối với tôi vì đây không phải là trường hợp sử dụng hấp dẫn.
Kylotan

1
-1 Từ tôi cũng vậy, vì nhiều lý do tương tự như Kylotan. Bạn không đưa ra một lập luận rất hấp dẫn, tất cả điều này có thể đã được thực hiện với các nhà xây dựng.
Paul Manta

Vâng, nó có thể được thực hiện với các hàm tạo và hàm hủy. Ông yêu cầu các trường hợp sử dụng của một kỹ thuật và tại sao và như thế nào, thay vì cách chúng hoạt động hoặc tại sao chúng làm. Có một hệ thống dựa trên thành phần nơi bạn có các phương thức setter / ràng buộc, so với các tham số được truyền bởi hàm tạo cho DI thực sự hoàn toàn phụ thuộc vào cách bạn muốn xây dựng giao diện của mình. Nhưng nếu đối tượng của bạn yêu cầu 20 thành phần IOC, bạn có muốn đặt TẤT CẢ chúng vào hàm tạo của mình không? Bạn có thể? Tất nhiên bạn có thể. Bạn có nên Co le không. Nếu bạn chọn không , thì bạn có cần .init, có thể không, nhưng có khả năng. Ergo, trường hợp hợp lệ.
Norguard

1
@Kylotan Tôi thực sự đã chỉnh sửa tiêu đề của câu hỏi để hỏi tại sao. OP chỉ hỏi "làm thế nào". Tôi đã mở rộng câu hỏi để bao gồm "tại sao" là "làm thế nào" là tầm thường đối với bất kỳ ai biết bất cứ điều gì về lập trình ("Chỉ cần di chuyển logic bạn sẽ có vào ctor thành một chức năng riêng biệt và gọi nó") và "tại sao" là thú vị hơn / chung chung.
Tetrad

17

Bất cứ điều gì bạn đọc nói rằng init và CleanUp là tốt hơn, cũng nên nói với bạn tại sao. Các bài viết không biện minh cho tuyên bố của họ là không đáng đọc.

Có các chức năng khởi tạo và tắt máy riêng biệt có thể giúp thiết lập và phá hủy hệ thống dễ dàng hơn vì bạn có thể chọn thứ tự để gọi chúng, trong khi các hàm tạo được gọi chính xác khi đối tượng được tạo và hàm hủy được gọi khi đối tượng bị phá hủy. Khi bạn có sự phụ thuộc phức tạp giữa 2 đối tượng, bạn thường cần cả hai tồn tại trước khi chúng tự thiết lập - nhưng thường thì đây là dấu hiệu của thiết kế kém ở nơi khác.

Một số ngôn ngữ không có công cụ hủy mà bạn có thể dựa vào, vì việc đếm tham chiếu và thu gom rác khiến cho việc biết khi nào đối tượng sẽ bị phá hủy sẽ khó khăn hơn. Trong các ngôn ngữ này, bạn hầu như luôn cần một phương thức tắt / dọn dẹp, và một số muốn thêm phương thức init cho đối xứng.


Cảm ơn bạn, nhưng tôi chủ yếu tìm kiếm các ví dụ, vì bài báo không có chúng. Tôi xin lỗi nếu câu hỏi của tôi không rõ ràng về điều đó, nhưng tôi đã chỉnh sửa nó ngay bây giờ.
Friso

3

Tôi nghĩ lý do tốt nhất là: cho phép gộp chung.
nếu bạn có init và CleanUp, bạn có thể, khi một đối tượng bị giết, chỉ cần gọi CleanUp và đẩy đối tượng lên một chồng đối tượng cùng loại: một 'pool'.
Sau đó, bất cứ khi nào bạn cần một đối tượng mới, bạn có thể bật một đối tượng từ nhóm HOẶC nếu nhóm đó trống - không tốt - bạn phải tạo một đối tượng mới. Sau đó, bạn gọi init trên đối tượng này.
Một chiến lược tốt là lấp đầy hồ bơi trước khi trò chơi bắt đầu với số lượng đối tượng 'tốt', do đó bạn không bao giờ phải tạo bất kỳ đối tượng gộp nào trong trò chơi.
Mặt khác, nếu bạn sử dụng 'mới' và chỉ dừng tham chiếu một đối tượng khi nó không có ích với bạn, bạn tạo ra rác phải được nhớ lại vào một lúc nào đó. Hồi ức này đặc biệt xấu đối với các ngôn ngữ đơn luồng như Javascript, nơi trình thu gom rác dừng tất cả mã khi đánh giá nó cần phải nhớ lại bộ nhớ của các đối tượng không còn sử dụng. Trò chơi bị treo trong vài mili giây và trải nghiệm chơi bị phá hỏng.
- Bạn đã hiểu rõ -: nếu bạn gộp tất cả các đối tượng của mình, không có hồi ức xảy ra, do đó không có sự chậm lại ngẫu nhiên nữa.

Việc gọi init trên một đối tượng đến từ nhóm cũng nhanh hơn nhiều so với việc cấp phát bộ nhớ + init một đối tượng mới.
Nhưng việc cải thiện tốc độ ít quan trọng hơn, vì việc tạo đối tượng thường không phải là nút cổ chai hiệu năng ... Với một vài ngoại lệ, như các trò chơi điên cuồng, động cơ hạt hoặc động cơ vật lý sử dụng các vectơ 2D / 3d chuyên sâu cho các tính toán của chúng. Ở đây cả tốc độ và tạo rác đều được cải thiện rất nhiều bằng cách sử dụng hồ bơi.

Rq: bạn có thể không cần phải có phương thức CleanUp cho các đối tượng được gộp chung của mình nếu init () đặt lại mọi thứ.

Chỉnh sửa: trả lời bài đăng này thúc đẩy tôi hoàn thành một bài viết nhỏ tôi đã thực hiện về tổng hợp trong Javascript .
Bạn có thể tìm thấy nó ở đây nếu bạn quan tâm:
http://gamealchemist.wordpress.com/


1
-1: Bạn không cần phải làm điều này chỉ để có một nhóm đối tượng. Bạn có thể làm điều đó bằng cách tách biệt phân bổ khỏi xây dựng thông qua vị trí mới và phân bổ khỏi xóa bằng một lệnh gọi hàm hủy rõ ràng. Vì vậy, đây không phải là một lý do hợp lệ để tách các hàm tạo / hàm hủy khỏi một số phương thức khởi tạo.
Nicol Bolas

vị trí mới là cụ thể C ++ và một chút bí truyền.
Kylotan

+1 có thể làm điều này theo cách khác trong c +. Nhưng không phải trong các ngôn ngữ khác ... và đây có lẽ chỉ là lý do tại sao tôi sẽ sử dụng phương thức init trên các trò chơi.
Kikaimaru

1
@Nicol Bolas: tôi nghĩ bạn đang phản ứng thái quá. Thực tế là có nhiều cách khác để thực hiện gộp (bạn đề cập đến một cách phức tạp, cụ thể đối với C ++) không làm mất hiệu lực thực tế rằng sử dụng một init riêng biệt là một cách hay và đơn giản để thực hiện gộp trong nhiều ngôn ngữ. sở thích của tôi, trên GameDev, cho câu trả lời chung chung hơn.
GameAlchemist

@VincentPiel: Làm thế nào là sử dụng vị trí mới và "phức tạp" như vậy trong C ++? Ngoài ra, nếu bạn đang làm việc trong ngôn ngữ GC, tỷ lệ cược là các đối tượng sẽ chứa các đối tượng dựa trên GC. Vì vậy, họ cũng sẽ phải thăm dò ý kiến ​​của từng người trong số họ? Do đó, việc tạo một đối tượng mới sẽ liên quan đến việc có được một loạt các đối tượng mới từ các nhóm.
Nicol Bolas

0

Câu hỏi của bạn bị đảo ngược ... Trong lịch sử, câu hỏi thích hợp hơn là:

Tại sao được xây dựng + intialisation lồng việc , tức là lý do tại sao không chúng ta thực hiện các bước riêng rẽ? Chắc chắn điều này đi ngược lại với SoC ?

Đối với C ++, ý định của RAII là việc thu thập và giải phóng tài nguyên được gắn trực tiếp với tuổi thọ đối tượng, với hy vọng rằng điều này sẽ đảm bảo việc phát hành tài nguyên. Phải không? Từng phần. Nó được hoàn thành 100% trong bối cảnh các biến tự động / dựa trên ngăn xếp, trong đó việc để phạm vi liên quan tự động gọi các hàm hủy / giải phóng các biến này (do đó là vòng loại automatic). Tuy nhiên, đối với các biến heap, mô hình rất hữu ích này đáng buồn bị phá vỡ, vì bạn vẫn buộc phải gọi một cách rõ ràng deleteđể chạy hàm hủy và nếu bạn quên làm như vậy, bạn vẫn sẽ bị cắn bởi những gì RAII cố gắng giải quyết; trong bối cảnh các biến được phân bổ heap, sau đó, C ++ cung cấp lợi ích hạn chế so với C ( deletevs.free()) trong khi kết hợp xây dựng với khởi tạo, điều này tác động tiêu cực đến các khía cạnh sau:

Xây dựng một hệ thống đối tượng cho các trò chơi / mô phỏng trong C được khuyến khích ở chỗ nó sẽ làm sáng tỏ rất nhiều về những hạn chế của RAII và các mẫu OO-centric khác thông qua sự hiểu biết sâu sắc hơn về các giả định mà C ++ và các ngôn ngữ OO cổ điển sau này tạo ra (hãy nhớ rằng C ++ khởi đầu là một hệ thống OO được xây dựng trong C).

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.