Bất kì mã của riêng bạn mà bạn đã không nhìn từ sáu tháng trở lên có thể cũng đã được viết bởi một người khác. Nathan Myhrvold
STDIO Factory là nơi “sản xuất” ra những Entity mà Developer cần dựa theo ID được truyền vào. Hiển nhiên số lượng loại của những Entity này là giới hạn và cá biệt do chúng được định nghĩa cụ thể trong Factory.
Nội dung bài viết

Giới thiệu

Cho đến hiện tại, chúng ta vẫn đang dùng hàm init tại main.cpp để đặc tả Entity, và chỉ mới có 1 Entity. Hãy thử tưởng tượng hàm init sẽ trở thành thế nào nếu ta đặc tả tất cả Entity trong một game tại đó? Chỉ tưởng tượng một hàm có thể dài vàn ngàn dòng thôi cũng thấy rùng mình.

Lúc này ta cần một cơ chế thông minh hơn, quy củ hơn để gói gọn các phương pháp khởi tạo Entity, để làm việc đó, tôi đã tạo ra một lớp quản lý khởi tạo Entity, tôi gọi đó là Factory.

Đây là project ví dụ dùng trong bài viết: CBP-9_Example.

Project được khởi tạo trên nền Visual Studio 2013, sử dụng Fred Framework do tôi định nghĩa, độc giả không cần cài thêm DirectX hoặc OpenGL để sử dụng, đây là framework làm việc với win32.

Tiền đề bài viết

Đây là bài viết thứ 9 của tôi trong chuỗi bài viết về CBP thuộc STDIO :: www.stdio.vn.

Đối tượng hướng đến

Các lập trình viên:

  • Mong muốn tham khảo một phương pháp hiện thực của CBP.
  • Có kiến thức nền tảng tương đối tốt về C++.

Các lập trình viên khác có thể sử dụng làm tài liệu tham khảo.

Danh mục các bài viết CBP

Các bài viết liên quan

Tổng quan tư tưởng

Nói một cách đơn giản, tôi đang tạo nên một tập hợp rất nhiều hàm, mỗi hàm tạo ra một loại Entity, sau đó gom tất cả vào một hàm chung, và dựa vào ID của Entity cần tạo để gọi hàm tương ứng. Phải, và đó chính là Factory Pattern. Và vì chỉ có một Factory mang chức năng tạo ra các Entity, nên tôi kết hợp thêm Singleton Pattern để tăng tính linh hoạt cho Factory (sau này có thể một số Entity sẽ sinh ra một số Entity khác trong Component của nó – chẳng hạn hoạt động “bắn đạn” – và chúng ta cần truy cập Factory từ những nơi đó.)

Những lưu ý ban đầu

  • Tùy vào thể loại game mà cách hiện thực các phương thức tạo Entity sẽ khác nhau.
    • Đối với những game như RPG, các Entity có xu hướng lặp lại về cơ chế hoạt động, AI, tính chất, chỉ thay đổi về hình ảnh và một số thuộc tính chỉ số. Loại này ta thường tạo các phương thức khởi tạo chung, 1 phương thức cho nhiều Entity, tất nhiên sẽ cần truyền đối số vào để thay đổi thuộc tính của Entity.
    • Một số game có những Entity rất khác nhau, mỗi hình ảnh là đại diện cho một loại Entity hoàn toàn khác, với AI hoàn toàn riêng biệt (hoặc gần như vậy). Những Entity này đòi hỏi thao tác khởi tạo và thiết lập chi tiết, nên các phương thức tạo thường không yêu cầu đối số nữa mà sẽ tạo hẳn luôn một phương thức mới cho mỗi loại Entity. Ban đầu ta có thể dựa vào việc đọc tập tin lưu trữ thông số Entity và truyền vào phương thức tạo để debug và cân bằng game. Nhưng một khi chắc chắn game đã ổn định thì một số thuộc tính static có thể giúp ta tăng performance cho game (dù vậy, điều này không thường xảy ra đối với các game có yêu cầu bảo trì, nâng cấp và cân bằng liên tục).
  • Factory include các Component, trong một số Component lại include Factory.
  • Hiện thực Factory theo cách thứ 2 có thể tạo ra một file cpp dài vài ngàn, thậm chí vài chục ngàn dòng. Đối với tôi mà nói, điều này bình thường, vì cách quản lý từng phương thức, cách tôi sắp xếp chúng và tận dụng sự hỗ trợ của IDE, chúng ta dễ dàng có được một file mã nguồn "đẹp" và dễ tìm kiếm. Vấn đề là khi file header của Factory dài hơn 300 dòng, có lẽ tôi sẽ suy nghĩ việc chia Factory ra nhiều Factory khác, chuyên biệt hơn và tích hợp lại vào Factory chung.
  • Mỗi lần gọi đến phương thức tạo Entity của Factory, nếu ID truyền vào là hợp lệ, một Entity sẽ được tạo ra, và được đưa về nhờ vào kiểu trả về của phương thức. Các Entity này không liên quan gì đến các Entity đã được tạo trước đó.

Entity ID

Đây là chìa khóa cho việc “sản xuất” Entity. Factory dựa theo ID được truyền vào để trả về Entity phù hợp.

Nghe có vẻ phức tạp, thực chất, đây cũng chỉ là một danh sách các ID được định nghĩa theo kiểu enum được đặt trong ID::Entity. Nhưng có một điều tôi cần lưu ý, đó là danh sách ID của chúng ta có thể rất rất rất dài, tùy vào độ phức tạp của project, có thể là 50, cũng có thể là 500, vì thế để có được một sự quản lý tốt nhất, tiện lợi cho việc tìm kiếm, thêm vào, xóa bỏ những ID, tôi khuyến khích độc giả sắp xếp tên của ID theo bảng chữ cái alphabet, và các phương thức tương ứng trong Factory cũng thế.

Hiện thực Factory

Với những lưu ý trên, tôi khai báo lớp Factory của mình như sau:

class Factory
{
public:
	// To create and return an Entity.
	// @param entityID: The ID to specify the Entity gonna create.
	Entity* create(ID::Entity entityID);

	static Factory* getInstance();

	// Must be call before use this Factory.
	static void init();

	// Call to release this Factory.
	static void release();

private:
	Factory();
	~Factory();

	// The list of method used to create each type of Entity.
	// Sorted by A->Z.
	Entity* create_STDIO_LOGO();
	Entity* create_STDIO_LOGO_CONTROLABLE();

private:
	static Factory* m_instance;
};

Các phương thức init, realease, getInstance và thuộc tính static m_instance được tôi dùng để hiện thực Singleton cho Factory, nhằm đảm bảo sự đơn giản và dễ hiểu, tôi sử dụng những phương thức và thuộc tính này như một quy tắc riêng của tôi, một phần khác cũng để cho toàn bộ team làm việc biết được có những lớp Singleton nào trong project (thông qua danh sách các lớp được gọi init không đối ở đầu chương trình).

Tại create, tôi cho 1 cấu trúc switch gọi phương thức tương ứng với ID::Entity được truyền vào, các phương thức được gọi có tên tương ứng với tên của ID, và được định nghĩa trong tầm vực private, các phương thức được sắp xếp tăng dần theo chuỗi ký tự của tên phương thức.

Chi tiết hiện thực từng phương thức tạo Entity cụ thể tương tự như hàm init tại main.cpp, hay chính xác hơn là tôi chuyển chúng vào đây. Chi tiết hơn độc giả có thể xem trong project ví dụ đính kèm.

Cài đặt và thử nghiệm

Chúng ta có một số thay đổi tại mã nguồn như sau:

// main.cpp
Entity* g_entity1;
Entity* g_entity2;
void update(float delta)
{
	g_entity1->update(delta);
	g_entity2->update(delta);
}
void draw(HDC hdc)
{
	g_entity1->draw(hdc);
	g_entity2->draw(hdc);
}
void init(HINSTANCE instance, HWND handler)
{
	FTexture::init(instance, handler);
	Factory::init();

	g_entity1 = Factory::getInstance()->create(ID::STDIO_LOGO);
	g_entity2 = Factory::getInstance()->create(ID::STDIO_LOGO_CONTROLABLE);
}
void release()
{
	FTexture::release();
	Factory::release();

	SAFE_DELETE(g_entity1);
	SAFE_DELETE(g_entity2);
}

Và chúng ta có gì nào? 2 Stdio Logo, 1 cái nằm yên tại vị trí (250, 250), còn 1 cái nhảy ngẫu nhiên khi ta ấn phím S, biến mất khi ta ấn phím H.

Thực ra Factory không được gọi trong init, đối với 1 game mà nói, Factory cần được gọi trong file cpp của map, khi ta đang load map. Đối với ví dụ nhỏ này tôi không hiện thực phần map, tuy nhiên vẫn mong độc giả lưu ý, tránh khởi tạo 1 lô các Entity trong init rồi “để dùng dần”.

Tổng kết

Qua bài viết này chúng ta đã hiện thực thêm một lớp hỗ trợ cho Developer trong quá trình khởi tạo Entity, thực ra đây chỉ là một cấu trúc nhằm gói gọn các khởi tạo của Entity trong quá trình sử dụng, về căn bản, phần định nghĩa từng Entity không thay đổi gì cả.

Đây chỉ là bề nổi của một Factory, khi tiến vào những project phức tạp hơn, độc giả có thể gặp những trường hợp khác, như khởi tạo sao chép Entity (Entity tạo ra không chỉ giống về chủng loại, mà cả các chỉ số khác cũng giữ nguyên so với đối tượng sao chép, ví dụ như HP, MP, vị trí, trạng thái…) chẳng hạn.

Bạn cần hỗ trợ các dự án kết nối không dây?

Quí doanh nghiệp, cá nhân cần hỗ trợ, hợp tác các dự án IoT, kết nối không dây. Vui lòng liên hệ, hoặc gọi trực tiếp 0942.111912.

  • TỪ KHÓA
  • Arduino
  • ESP32
  • ESP8266
  • Wifi
  • Bluetooth
  • Zigbee
  • Raspberry Pi
THẢO LUẬN
ĐÓNG