2010-08-02 34 views
6

Tôi đang sử dụng một nhà máy trừu tượng để tạo các thành phần giao diện người dùng như hộp thoại. Nhà máy trừu tượng được sử dụng được trả lại từ một "INode" chung hiện được chọn, là lớp cơ sở cho một số loại nút khác nhau. Vì vậy, ví dụ, nếu tôi muốn thêm một nút mới cùng loại như các nút chọn, kịch bản đi một cái gì đó như thế này:Sự cố khi sử dụng nhà máy trừu tượng

(xin lưu ý đây là mã bán pseudo)

Người dùng nhấp nút và nút được lưu trữ để sử dụng sau:

void onTreeNodeSelected(INode *node) 
{ 
    selectedNode = node; 
} 

người dùng nhấp "add" vào giao diện người dùng:

void onAddClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createAddDialog(parentWidget); 
    dialog->show(); 
} 

Mà tất cả có vẻ tốt đẹp. Sự cố xảy ra khi tôi muốn chỉnh sửa nút đã chọn:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget); 
    dialog->show(); 
} 

Oh dear .. Tôi đang chuyển đối tượng INode. Tại một số điểm tôi sẽ phải downcast rằng để các loại nút chính xác để hộp thoại có thể sử dụng nó đúng cách.

Tôi đã nghiên cứu mã nguồn "PostgreSQL Admin 3" và chúng thực hiện tương tự như vậy. Họ làm tròn nó bằng cách làm một cái gì đó như thế này:

FooObjectFactoryClass::createDialog(IObject *object) 
{ 
    FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object); 
} 

Yeck .. cast!

Cách duy nhất tôi có thể nghĩ xung quanh nó và vẫn có thể sử dụng các nhà máy của tôi là để tiêm nút riêng của mình vào nhà máy trước khi nó được trả về:

FooNode : INode 
{ 
    FooNodeFactory* FooNode::getFactory() 
    { 
     fooNodeFactory->setFooNode(this); 
     return fooNodeFactory; 
    } 
} 

Vì vậy, sau đó chỉnh sửa sự kiện của tôi có thể làm điều này:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(parentWidget); 
    dialog->show(); 
} 

Và nó sẽ sử dụng nút được chèn cho ngữ cảnh.

Tôi giả sử nếu không có mã được tiêm, createEditDialog có thể xác nhận sai hoặc cái gì đó.

Mọi suy nghĩ?

Cảm ơn!

Trả lời

3

Giải pháp chung là "công văn kép", trong đó bạn gọi hàm ảo trên một đối tượng, lần lượt gọi hàm ảo trên đối tượng khác, đi qua this, hiện có loại tĩnh chính xác. Vì vậy, trong trường hợp của bạn, các nhà máy có thể chứa "tạo ra" chức năng cho các loại khác nhau của các cuộc đối thoại:

class IFactory 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(ThisNode*, IWidget*); 
    virtual Dialog* createEditDialog(ThatNode*, IWidget*); 
    virtual Dialog* createEditDialog(TheOtherNode*, IWidget*); 
    .... 
}; 

sau đó từng loại node có một ảo createEditDialog rằng công văn đến các chức năng nhà máy đúng:

class INode 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(IWidget* parent) = 0; 
    .... 
}; 

class ThisNode : public INode 
{ 
public: 
    .... 
    virtual Dialog* ThisNode::createEditDialog(IWidget* parent) 
    { 
     return getFactory()->createEditDialog(this, parent); 
    } 
    .... 
}; 

Sau đó, bạn có thể tạo ra cuộc đối thoại đúng như

void onEditClicked() 
{ 
    Dialog *dialog = selectedNode->createEditDialog(parentWidget); 
    dialog->show(); 
} 
1

Theo ý kiến ​​của tôi, sử dụng kiểu chữ C (mặc dù kiểu C++ sẽ được ưu tiên) là hoàn toàn chấp nhận được miễn là mã của bạn được nhận xét đúng.

Tôi không phải là người hâm mộ lớn của DI (dependency injection) vì nó làm cho một số mã khó tuân theo và, trong trường hợp của bạn, tôi thà xem dynamic_cast<>() hoặc một cái gì đó hơn là cố gắng theo mã được tiêm trên nhiều tệp nguồn.

0

Tôi sẽ kiện hai điều.

Đầu tiên: không có gì sai khi truyền. Nếu bạn muốn an toàn, bạn có thể sử dụng RTTI (công cụ type_id) hoặc một số hàm ảo trong lớp INode có thể trả về một số thông tin sẽ cho bạn biết nếu nó an toàn để truyền.

Thứ hai: bạn có thể kiểm tra xem hàm createEditDialog cần gì và đặt các hàm đó vào các hàm ảo trong INode hoặc lớp được thừa kế sẽ là kiểu createDialog mong đợi.

Nói chung, tôi thấy không có gì thực sự sai với vấn đề bạn mô tả, và thật khó để đưa ra nhiều đề xuất hơn mà không nhìn thấy toàn bộ mã mà tôi cho là không khả thi.

0

cách tiếp cận của bạn chích nút vào nhà máy nói chung là một mô hình tôi đã tìm thấy hữu ích, nhưng thường có lần khi bạn không có tham chiếu đến đối tượng mục tiêu khi bạn tạo nhà máy giống như bạn làm ở đây. Vì vậy, điều này có thể làm việc tốt cho bạn trong trường hợp này và nó đơn giản hơn xử lý loại vấn đề này trong trường hợp chung.

Đối với trường hợp tổng quát hơn, bạn cần làm việc với khái niệm giao diện và thiết lập một cơ chế mà đối tượng INode của bạn có thể xuất bản giao diện nào hỗ trợ và cung cấp quyền truy cập vào các giao diện đó cho khách hàng. Làm điều này hoàn toàn tự động dẫn đến một cách tiếp cận giống như COM yêu cầu đăng ký và truyền động. Nhưng bạn cũng có thể làm điều này theo kiểu được gõ tĩnh nếu bạn có một bộ giao diện tương đối ổn định mà bạn muốn phơi bày và có thể đủ khả năng chỉnh sửa giao diện INode khi bạn cần thêm giao diện thành phần mới.

Vì vậy, đây sẽ là một ví dụ về cách làm đơn giản phương pháp tĩnh gõ:

struct INode 
{ 
    virtual INodeSize* getNodeSizeInterface() = 0; 

    virtual INodeProperties* getNodePropertiesInterface() = 0; 

    virtual INodeColor* getNodeColorInterface() = 0; 

    ... // etc 
} 

Bây giờ mỗi thực hiện INode có thể trở lại một số hoặc tất cả các giao diện thành phần (nó sẽ chỉ trả về NULL nếu nó didn' t thực hiện chúng). Sau đó, các hộp thoại của bạn hoạt động trên các giao diện thành phần để thực hiện công việc của chúng thay vì cố gắng tìm ra thực thi thực tế của INode được truyền vào. Điều này sẽ làm cho việc ánh xạ linh hoạt hơn giữa các hộp thoại và các triển khai nút. Một hộp thoại có thể nhanh chóng xác định xem nó có đối tượng "tương thích" INode bằng cách xác minh rằng đối tượng đó trả về một đối tượng hợp lệ cho mỗi giao diện mà hộp thoại quan tâm.

0

Tôi nghĩ rằng một diễn viên bên trong trường hợp này không phải là điều xấu. , mặc dù bạn từ bỏ việc kiểm tra thời gian biên dịch. Nếu loại nút không thay đổi khi chạy, bạn có thể sử dụng các mẫu thay vì một lớp trừu tượng INode.

Nếu không, giải pháp được đề xuất của bạn là giải pháp mà tôi cũng sẽ nghĩ đến. Tuy nhiên, tôi sẽ đổi tên Phương thức thành một cái gì đó như "getSelectedNodeDialogFactory" (tôi biết, tên dài) để rõ ràng rằng nhà máy trả lại là cụ thể cho nút đó. Có các hộp thoại khác cần biết loại bê tông của đối tượng INode không? Có phải createAddDialog cần có nút cha hoặc nút tiền thân, có thể không? Tất cả những thứ đó đều có thể đi trong lớp nhà máy-với-chọn-nút.

Các vấn đề liên quan