Thiết kế dựa trên thành phần: xử lý tương tác đối tượng


9

Tôi không chắc chắn chính xác làm thế nào các đối tượng làm mọi thứ với các đối tượng khác trong một thiết kế dựa trên thành phần.

Nói rằng tôi có một Objlớp học. Tôi làm:

Obj obj;
obj.add(new Position());
obj.add(new Physics());

Làm thế nào tôi có thể có một đối tượng khác không chỉ di chuyển quả bóng mà còn áp dụng những vật lý đó. Tôi không tìm kiếm chi tiết thực hiện mà chỉ trừu tượng cách các đối tượng giao tiếp. Trong một thiết kế dựa trên thực thể, bạn có thể có:

obj1.emitForceOn(obj2,5.0,0.0,0.0);

Bất kỳ bài viết hoặc giải thích nào để hiểu rõ hơn về thiết kế hướng thành phần và cách thực hiện những điều cơ bản sẽ thực sự hữu ích.

Câu trả lời:


10

Điều đó thường được thực hiện bằng cách sử dụng tin nhắn. Bạn có thể tìm thấy rất nhiều chi tiết trong các câu hỏi khác trên trang web này, như ở đây hoặc ở đó .

Để trả lời ví dụ cụ thể của bạn, một cách để đi là xác định một Messagelớp nhỏ mà các đối tượng của bạn có thể xử lý, ví dụ:

struct Message
{
    Message(const Objt& sender, const std::string& msg)
        : m_sender(&sender)
        , m_msg(msg) {}
    const Obj* m_sender;
    std::string m_msg;
};

void Obj::Process(const Message& msg)
{
    for (int i=0; i<m_components.size(); ++i)
    {
        // let components do some stuff with msg
        m_components[i].Process(msg);
    }
}

Bằng cách này, bạn không "làm ô nhiễm" bạn Obj giao diện lớp với các phương thức liên quan đến thành phần. Một số thành phần có thể chọn xử lý tin nhắn, một số có thể bỏ qua nó.

Bạn có thể bắt đầu bằng cách gọi phương thức này trực tiếp từ một đối tượng khác:

Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);

Trong trường hợp này, obj2chúng Physicstôi sẽ chọn tin nhắn và thực hiện bất kỳ xử lý nào cần thực hiện. Khi hoàn thành, nó sẽ:

  • Gửi tin nhắn "SetPocation" cho chính mình, rằng Position thành phần sẽ chọn;
  • Hoặc truy cập trực tiếp vào Positionthành phần để sửa đổi (khá sai đối với thiết kế dựa trên thành phần thuần túy, vì bạn không thể giả sử mọi đối tượng đều có một Positionthành phần, nhưng Positionthành phần đó có thể là một yêu cầu Physics).

Nói chung, nên trì hoãn việc xử lý thông báo thực tế đến bản cập nhật của thành phần tiếp theo . Xử lý nó ngay lập tức có thể có nghĩa là gửi tin nhắn đến các thành phần khác của các đối tượng khác, vì vậy gửi chỉ một tin nhắn có thể nhanh chóng có nghĩa là một chồng spaghetti không thể khắc phục.

Bạn có thể sẽ phải sử dụng một hệ thống nâng cao hơn sau này: hàng đợi tin nhắn không đồng bộ, gửi tin nhắn đến nhóm đối tượng, đăng ký theo từng thành phần / không đăng ký từ tin nhắn, v.v.

Các Messagelớp có thể là một thùng chứa chung cho một chuỗi đơn giản như trên, nhưng chế biến dây trong thời gian chạy là không thực sự hiệu quả. Bạn có thể tìm một thùng chứa các giá trị chung: chuỗi, số nguyên, số float ... Với một tên hoặc tốt hơn là ID, để phân biệt các loại thông báo khác nhau. Hoặc bạn cũng có thể lấy được một lớp cơ sở để phù hợp với nhu cầu cụ thể. Trong trường hợp của bạn, bạn có thể tưởng tượng một EmitForceMessagexuất phát từ Messagevà thêm vectơ lực mong muốn - nhưng hãy cẩn thận với chi phí thời gian chạy của RTTI nếu bạn làm như vậy.


3
Tôi sẽ không lo lắng về "sự không tinh khiết" của việc truy cập trực tiếp các thành phần. Các thành phần được sử dụng để phục vụ nhu cầu chức năng và thiết kế, không phải học viện. Bạn muốn kiểm tra xem một thành phần có tồn tại không (ví dụ: kiểm tra giá trị trả về không phải là null đối với lệnh gọi thành phần).
Sean Middleditch

Tôi đã luôn nghĩ về nó như lần trước bạn đã nói, sử dụng RTTI nhưng rất nhiều người đã nói rất nhiều điều tồi tệ về RTTI
jmasterx

@SeanMiddleditch Chắc chắn, tôi sẽ làm theo cách này, chỉ cần đề cập đến điều đó để làm rõ rằng bạn phải luôn kiểm tra kỹ những gì bạn đang làm khi truy cập các thành phần khác của cùng một thực thể.
Laurent Couvidou

@Milo Trình RTTI do trình biên dịch triển khai và nó dynamic_cast có thể trở thành nút cổ chai, nhưng tôi không lo lắng về điều đó cho đến bây giờ. Bạn vẫn có thể tối ưu hóa điều này sau này nếu nó trở thành một vấn đề. Định danh lớp dựa trên CRC hoạt động như một nét duyên dáng.
Laurent Couvidou

Ỏitemplate <tên chữ T> uint32_t class_id () {static uint32_t v; trả lại (uint32_t) & v; } Ạc - không cần RTTI.
arul

3

Những gì tôi đã làm để giải quyết một vấn đề tương tự như những gì bạn thể hiện, là thêm một số trình xử lý thành phần cụ thể và thêm một số loại hệ thống giải quyết sự kiện.

Vì vậy, trong trường hợp đối tượng "Vật lý" của bạn, khi nó được khởi tạo, nó sẽ tự thêm vào một trình quản lý trung tâm của objetcs Vật lý. Trong vòng lặp trò chơi, các loại người quản lý này có bước cập nhật riêng, do đó, khi Trình quản lý vật lý này được cập nhật, nó sẽ tính toán tất cả các tương tác vật lý và thêm chúng vào hàng đợi sự kiện.

Sau khi bạn tạo tất cả các sự kiện của mình, bạn có thể giải quyết hàng đợi sự kiện của mình chỉ bằng cách kiểm tra những gì đã xảy ra và thực hiện hành động theo cách của bạn, trong trường hợp của bạn, sẽ có một sự kiện nói rằng đối tượng A và B tương tác bằng cách nào đó, vì vậy bạn gọi phương thức emitForceOn của mình.

Ưu điểm của phương pháp này:

  • Về mặt khái niệm, thực sự đơn giản để làm theo.
  • Cung cấp cho bạn chỗ để tối ưu hóa cụ thể như sử dụng bộ tứ hoặc bất cứ thứ gì bạn cần.
  • Nó kết thúc thực sự là "cắm và chơi". Các đối tượng với vật lý không tương tác với các đối tượng phi vật lý vì chúng không tồn tại cho người quản lý.

Nhược điểm:

  • Bạn kết thúc với rất nhiều tài liệu tham khảo di chuyển xung quanh, do đó, có thể trở nên hơi lộn xộn để làm sạch chính xác mọi thứ nếu bạn không cẩn thận (từ thành phần của bạn đến chủ sở hữu thành phần, từ người quản lý đến thành phần, từ sự kiện đến người tham gia, v.v. ).
  • Bạn phải đặt suy nghĩ đặc biệt vào thứ tự mà bạn giải quyết mọi thứ. Tôi đoán đó không phải là trường hợp của bạn, nhưng tôi đã phải đối mặt với nhiều vòng lặp vô hạn trong đó một sự kiện đã tạo ra một sự kiện khác và tôi chỉ cần thêm nó vào hàng đợi sự kiện.

Tôi hi vọng cái này giúp được.

Tái bút: Nếu ai đó có cách sạch hơn / tốt hơn để giải quyết vấn đề này, tôi thực sự muốn nghe.


1
obj->Message( "Physics.EmitForce 0.0 1.1 2.2" );
// and some variations such as...
obj->Message( "Physics.EmitForce", "0.0 1.1 2.2" );
obj->Message( "Physics", "EmitForce", "0.0 1.1 2.2" );

Một vài điều cần lưu ý về thiết kế này:

  • Tên của thành phần là tham số đầu tiên - điều này là để tránh có quá nhiều mã hoạt động trên tin nhắn - chúng tôi không thể biết thành phần nào mà bất kỳ tin nhắn nào có thể kích hoạt - và chúng tôi không muốn tất cả chúng nhai một tin nhắn với thất bại 90% tỷ lệ chuyển đổi thành nhiều nhánh và strcmp không cần thiết .
  • Tên của tin nhắn là tham số thứ hai.
  • Dấu chấm đầu tiên (ở # 1 và # 2) là không cần thiết, nó chỉ giúp đọc dễ dàng hơn (đối với mọi người, không phải máy tính).
  • Đó là sscanf, iostream, tên bạn tương thích. Không có cú pháp đường mà không làm gì để đơn giản hóa việc xử lý tin nhắn.
  • Tham số một chuỗi: truyền các kiểu gốc không rẻ hơn về mặt yêu cầu bộ nhớ vì bạn phải hỗ trợ một số lượng tham số chưa biết thuộc loại tương đối không xác định.
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.