Làm cách nào để sử dụng chính xác singletons trong lập trình công cụ C ++?


16

Tôi biết singletons rất tệ, công cụ trò chơi cũ của tôi đã sử dụng một đối tượng 'Trò chơi' đơn lẻ xử lý tất cả mọi thứ từ việc giữ tất cả dữ liệu đến vòng lặp trò chơi thực tế. Bây giờ tôi đang làm một cái mới.

Vấn đề là, để vẽ một cái gì đó trong SFML bạn sử dụng window.draw(sprite)trong đó cửa sổ là một sf::RenderWindow. Có 2 lựa chọn tôi thấy ở đây:

  1. Tạo một đối tượng Trò chơi đơn lẻ mà mọi thực thể trong trò chơi lấy ra (những gì tôi đã sử dụng trước đó)
  2. Biến điều này thành hàm tạo cho các thực thể: Entity(x, y, window, view, ...etc)(điều này thật vô lý và gây phiền nhiễu)

Điều gì sẽ là cách thích hợp để làm điều này trong khi giữ cho hàm tạo của Thực thể chỉ là x và y?

Tôi có thể thử và theo dõi mọi thứ tôi tạo trong vòng lặp trò chơi chính, và chỉ cần tự vẽ sprite của chúng trong vòng lặp trò chơi, nhưng điều đó có vẻ lộn xộn và tôi cũng muốn kiểm soát hoàn toàn toàn bộ chức năng vẽ cho thực thể.


1
Bạn có thể vượt qua cửa sổ dưới dạng đối số của chức năng 'kết xuất'.
dari

25
Singletons không tệ! chúng có thể hữu ích và đôi khi cần thiết (tất nhiên nó gây tranh cãi).
ExOfDe

3
Hãy thoải mái thay thế singletons bằng toàn cầu. Không có điểm nào trong việc tạo ra các tài nguyên được yêu cầu trên toàn cầu "theo yêu cầu", không có điểm nào trong việc chuyển chúng đi khắp nơi. Đối với các thực thể, bạn có thể sử dụng một lớp "cấp", để giữ một số thứ có liên quan đến tất cả chúng.
rắn5

Tôi khai báo cửa sổ của tôi và các phụ thuộc khác trong chính của tôi, và sau đó tôi có con trỏ trong các lớp khác.
KaareZ

1
@JAB Dễ dàng sửa với khởi tạo thủ công từ hàm main (). Khởi tạo lười biếng làm cho nó xảy ra tại một thời điểm không xác định, đó không phải là một ý tưởng tốt cho các hệ thống cốt lõi, bao giờ hết.
rắn5

Câu trả lời:


3

Chỉ lưu trữ dữ liệu cần thiết để hiển thị sprite bên trong mỗi thực thể, sau đó truy xuất nó từ thực thể và chuyển nó đến cửa sổ để kết xuất. Không cần lưu trữ bất kỳ cửa sổ hoặc xem dữ liệu bên trong các thực thể.

Bạn có thể có một lớp Game hoặc Engine cấp cao nhất chứa một lớp Level (chứa tất cả các thực thể hiện đang được sử dụng) và một lớp Renderer (chứa cửa sổ, khung nhìn và bất cứ thứ gì khác để kết xuất).

Vì vậy, vòng lặp cập nhật trò chơi trong lớp cấp cao nhất của bạn có thể trông như sau:

EntityList entities = mCurrentLevel.getEntities();
for(auto& i : entities){
  // Run game logic...
  i->update(...);
}
// Render all the entities
for(auto& i : entities){
  mRenderer->draw(i->getSprite());
}

3
Không có gì lý tưởng về một người độc thân. Tại sao phải thực hiện công khai nội bộ khi bạn không phải làm vậy? Tại sao viết Logger::getInstance().Log(...)thay vì chỉ Log(...)? Tại sao lại khởi tạo lớp một cách ngẫu nhiên khi được hỏi liệu bạn có thể thực hiện thủ công chỉ một lần không? Một hàm toàn cục tham chiếu toàn cầu tĩnh chỉ đơn giản hơn nhiều để tạo và sử dụng.
rắn5

@ Snake5 Biện minh cho những người độc thân trên Stack Exchange giống như đồng cảm với Hitler.
Dê Willy

30

Cách tiếp cận đơn giản là chỉ làm cho thứ đã từng là Singleton<T>toàn cầu Tthay thế. Các quả cầu cũng có vấn đề, nhưng chúng không đại diện cho một loạt các công việc phụ và mã soạn sẵn để thực thi một ràng buộc tầm thường. Về cơ bản, đây là giải pháp duy nhất không liên quan (có khả năng) chạm vào hàm tạo thực thể.

Cách tiếp cận khó khăn hơn, nhưng có thể tốt hơn là chuyển các phụ thuộc của bạn đến nơi bạn cần chúng . Vâng, điều này có thể liên quan đến việc chuyển một Window *loạt các đối tượng (như thực thể của bạn) theo cách có vẻ thô thiển. Thực tế là nó có vẻ thô thiển sẽ cho bạn biết một điều: thiết kế của bạn có thể là thô.

Lý do điều này khó khăn hơn (ngoài việc liên quan đến việc gõ nhiều hơn) là vì điều này thường dẫn đến việc cấu trúc lại các giao diện của bạn để điều bạn "cần" vượt qua là cần thiết bởi các lớp cấp độ lá ít hơn. Điều này làm cho rất nhiều sự xấu xí vốn có khi chuyển trình kết xuất của bạn đến mọi thứ biến mất và nó cũng cải thiện khả năng duy trì chung của mã của bạn bằng cách giảm số lượng phụ thuộc và khớp nối, mức độ mà bạn thực hiện rất rõ ràng bằng cách lấy các phụ thuộc làm tham số . Khi các phụ thuộc là singletons hoặc toàn cầu, việc các hệ thống của bạn kết nối với nhau không rõ ràng như thế nào.

Nhưng nó có khả năng là một công việc chính . Làm điều đó với một hệ thống sau khi thực tế có thể hết sức đau đớn. Bây giờ bạn có thể thực tế hơn nhiều khi chỉ cần để hệ thống của mình một mình, với singleton (đặc biệt là nếu bạn đang cố gắng thực sự vận chuyển một trò chơi hoạt động tốt, người chơi thường không quan tâm nếu bạn có một hoặc bốn trong đó).

Nếu bạn muốn thử làm điều này với thiết kế hiện tại của mình, bạn có thể cần phải đăng nhiều chi tiết hơn về việc triển khai hiện tại của mình vì thực sự không có một danh sách kiểm tra chung để thực hiện những thay đổi này. Hoặc đến thảo luận về nó trong trò chuyện .

Từ những gì bạn đã đăng, tôi nghĩ rằng một bước tiến lớn theo hướng "không độc thân" sẽ là để tránh sự cần thiết cho các thực thể của bạn có quyền truy cập vào cửa sổ hoặc chế độ xem. Điều đó cho thấy rằng họ tự vẽ và bạn không cần phải có thực thể tự vẽ . Bạn có thể áp dụng một phương pháp trong đó các thực thể chỉ chứa thông tin sẽ cho phépchúng được vẽ bởi một số hệ thống bên ngoài (có cửa sổ và xem các tham chiếu). Thực thể chỉ hiển thị vị trí của nó và sprite mà nó nên sử dụng (hoặc một số loại tham chiếu cho sprite đã nói, nếu bạn muốn lưu trữ các sprite thực tế trong chính trình kết xuất để tránh có các trường hợp trùng lặp). Trình kết xuất được yêu cầu đơn giản là vẽ một danh sách cụ thể các thực thể mà nó lặp lại, đọc dữ liệu từ đó và sử dụng đối tượng cửa sổ được giữ bên trong của nó để gọi drawvới sprite tìm kiếm thực thể.


3
Tôi không quen thuộc với C ++, nhưng không có khung tiêm phụ thuộc thoải mái cho ngôn ngữ này?
bgusach

1
Tôi sẽ không mô tả bất kỳ ai trong số họ là "thoải mái", và tôi không thấy chúng đặc biệt hữu ích nói chung, nhưng những người khác có thể có kinh nghiệm khác với họ vì vậy đó là một điểm tốt để đưa họ lên.

1
Phương pháp mà ông mô tả là làm cho nó để các thực thể không tự vẽ chúng mà giữ thông tin và một hệ thống duy nhất xử lý việc vẽ tất cả các thực thể được sử dụng rất nhiều trong hầu hết các công cụ trò chơi phổ biến hiện nay.
Patrick W. McMahon

1
+1 cho "Thực tế là nó có vẻ thô thiển sẽ cho bạn biết một điều: thiết kế của bạn có thể là thô."
Shadow503

+1 để đưa ra cả trường hợp lý tưởng và câu trả lời thực dụng.

6

Kế thừa từ sf :: RenderWindow

SFML thực sự khuyến khích bạn kế thừa từ các lớp của nó.

class GameWindow: public sf::RenderWindow{};

Từ đây, bạn tạo các hàm vẽ thành viên để vẽ các thực thể.

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity);
};

Bây giờ bạn có thể làm điều này:

GameWindow window;
Entity entity;

window.draw(entity);

Bạn thậm chí có thể thực hiện bước này xa hơn nếu Thực thể của bạn sẽ giữ các họa tiết độc đáo của riêng mình bằng cách tạo Thực thể kế thừa từ sf :: Sprite.

class Entity: public sf::Sprite{};

Bây giờ sf::RenderWindowchỉ có thể vẽ các Thực thể và các thực thể hiện có các hàm như setTexture()setColor(). Thực thể thậm chí có thể sử dụng vị trí của sprite làm vị trí riêng của nó, cho phép bạn sử dụng setPosition()chức năng cho cả việc di chuyển Thực thể VÀ sprite của nó.


Cuối cùng , nó khá đẹp nếu bạn chỉ có:

window.draw(game);

Dưới đây là một số triển khai ví dụ nhanh

class GameWindow: public sf::RenderWindow{
 sf::Sprite entitySprite; //assuming your Entities don't need unique sprites.
public:
 void draw(const Entity& entity){
  entitySprite.setPosition(entity.getPosition());
  sf::RenderWindow::draw(entitySprite);
 }
};

HOẶC LÀ

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity){
  sf::RenderWindow::draw(entity.getSprite()); //assuming Entities hold their own sprite.
 }
};

3

Bạn tránh các singletons trong phát triển trò chơi giống như cách bạn tránh chúng trong mọi loại phát triển phần mềm khác: bạn vượt qua các phụ thuộc .

Với điều đó ra khỏi đường đi, bạn có thể chọn để vượt qua sự phụ thuộc trực tiếp như các loại trần (như int, Window*, vv) hoặc bạn có thể chọn để vượt qua họ trong một hoặc nhiều tùy chỉnh wrapper loại (như EntityInitializationOptions).

Cách trước có thể gây khó chịu (như bạn đã tìm ra), trong khi cách sau sẽ cho phép bạn chuyển mọi thứ trong một đối tượng và sửa đổi các trường (và thậm chí chuyên về loại tùy chọn) mà không phải đi xung quanh và thay đổi mọi hàm tạo thực thể. Tôi nghĩ rằng cách thứ hai là tốt hơn.


3

Singletons không tệ. Thay vào đó họ rất dễ lạm dụng. Mặt khác, toàn cầu thậm chí còn dễ lạm dụng hơn và có nhiều vấn đề hơn.

Lý do duy nhất hợp lệ để thay thế một người độc thân bằng toàn cầu là để bình định những người ghét đơn tôn giáo.

Vấn đề là có một thiết kế bao gồm các lớp chỉ tồn tại một thể hiện toàn cầu duy nhất và có thể truy cập được từ mọi nơi. Điều này sẽ phá vỡ ngay khi bạn kết thúc có nhiều phiên bản của singleton, ví dụ như trong một trò chơi khi bạn triển khai màn hình chia nhỏ hoặc trong một ứng dụng doanh nghiệp đủ lớn khi bạn nhận thấy một logger duy nhất không phải lúc nào cũng là một ý tưởng tuyệt vời .

Tóm lại, nếu bạn thực sự có một lớp trong đó bạn có một thể hiện toàn cầu duy nhất mà bạn không thể vượt qua một cách hợp lý bằng tham chiếu , singleton thường là một trong những giải pháp tốt hơn trong một nhóm các giải pháp tối ưu.


1
Tôi là một người ghét tôn giáo độc thân và tôi cũng không coi một giải pháp toàn cầu. : S
Dan Pantry

1

Tiêm phụ thuộc. Một lợi ích của việc này là bây giờ bạn có thể tạo ra nhiều loại phụ thuộc này thông qua một nhà máy. Thật không may, xé toạc những người độc thân ra khỏi một lớp học sử dụng chúng giống như kéo một con mèo bằng chân sau qua một tấm thảm. Nhưng nếu bạn tiêm chúng, bạn có thể trao đổi các triển khai, có thể đang hoạt động.

RenderSystem(IWindow* window);

Bây giờ bạn có thể tiêm nhiều loại cửa sổ. Điều này cho phép bạn viết các bài kiểm tra đối với RenderSystem với nhiều loại cửa sổ khác nhau để bạn có thể thấy RenderSystem của bạn sẽ phá vỡ hoặc thực hiện như thế nào. Điều này là không thể, hoặc khó khăn hơn, nếu bạn sử dụng singletons trực tiếp bên trong "RenderSystem".

Bây giờ nó có thể kiểm tra nhiều hơn, mô-đun, và nó cũng được tách ra từ một triển khai cụ thể. Nó chỉ phụ thuộc vào một giao diện, không phải là một triển khai cụ thể.

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.