2010-07-25 28 views
6

Tôi có giao diện người dùng với chế độ xem dạng cây ở bên trái và trình xem bên phải (giống như ứng dụng email). Người xem ở bên phải hiển thị chi tiết của bất cứ điều gì tôi đã chọn trong cây bên trái.Cách tốt nhất để thực hiện các thao tác trên các nút cây, tốt nhất là không sử dụng các khách truy cập

Giao diện người dùng có các nút "thêm", "chỉnh sửa" và "xóa". Các nút này hoạt động khác nhau tùy thuộc vào "nút" trong cây được chọn.

Nếu tôi có nút của một loại cụ thể được chọn và người dùng nhấp vào "chỉnh sửa", thì tôi cần mở hộp thoại chỉnh sửa thích hợp cho loại nút cụ thể đó, với chi tiết của nút đó.

Bây giờ, có rất nhiều loại nút khác nhau và triển khai lớp khách truy cập cảm thấy hơi lộn xộn (hiện tại khách truy cập của tôi có khoảng 48 mục ....). Nó hoạt động độc đáo mặc dù - về cơ bản để chỉnh sửa một cái gì đó giống như một lớp OpenEditDialog kế thừa khách truy cập và mở hộp thoại chỉnh sửa thích hợp:

abstractTreeNode-> accept (OpenEditDialog());

Vấn đề là tôi phải triển khai lớp khách truy cập trừu tượng cho mọi "hành động" mà tôi muốn thực hiện trên nút và vì một lý do nào đó tôi không thể không nghĩ rằng mình thiếu một mẹo.

Một cách khác có thể của từng để thực hiện các chức năng trong các nút tự:

abstractTreeNode->openEditDialog(); 

Tôi Ording nút xung quanh một chút ở đây, vì vậy có lẽ đây là tốt hơn:

abstractTreeNode->editClickedEvent(); 

Tôi không thể không nghĩ rằng điều này là gây ô nhiễm các nút mặc dù.

Tôi đã nghĩ ra cách thứ ba mà tôi chưa đưa ra nhiều suy nghĩ. Tôi có thể có một lớp wrapper templated được thêm vào cây thay vì cho phép tôi có thể gọi miễn phí-chức năng để thực hiện bất kỳ hành động, vì vậy tôi đoán vì nó hoạt động như một đi giữa các nút và giao diện:

(pseudo code ra khỏi đỉnh đầu của tôi chỉ đưa ra một ý tưởng):

template <class T> 
TreeNode(T &modelNode) 
{ 
    m_modelNode = modelNode; 
} 

template <> 
void TreeNode<AreaNode>::editClickedEvent() 
{ 
    openEditDialog(m_modelNode); // Called with concrete AreaNode 
} 

template <> 
void TreeNode<LocationNode>::editClickedEvent() 
{ 
    openEditDialog(m_modelNode); // Called with concrete LocationNode 
} 

vv ..

Vì vậy, đây được mở rộng một cách hiệu quả các nút nhưng theo một cách khác nhau để sử dụng khách truy cập và có vẻ như một chút gọn gàng .

Bây giờ trước khi tôi tiếp tục và thực hiện việc sử dụng một trong các phương pháp này, tôi nghĩ sẽ rất khôn ngoan để có được một số đầu vào.

Cảm ơn! Tôi hy vọng tất cả điều này làm cho một số ý nghĩa ..

EDIT:

Tôi đã chế giễu lên ý tưởng wrapper templated ..

class INode 
{ 
public: 
    virtual ~INode() {} 
    virtual void foo() = 0; 
}; 

class AreaNode : public INode 
{ 
public: 
    AreaNode() {} 
    virtual ~AreaNode() {} 
    void foo() { printf("AreaNode::foo\r\n"); } 
}; 

class RoleNode : public INode 
{ 
public: 
    RoleNode() {} 
    virtual ~RoleNode() {} 
    void foo() { printf("RoleNode::foo\r\n"); } 
}; 

class ITreeNode 
{ 
public: 
    virtual ~ITreeNode() {} 
    virtual void bar() = 0; 
    virtual void foo() = 0; 
}; 

template <class T> 
class MainViewTreeNode : public ITreeNode 
{ 
public: 
    MainViewTreeNode() : m_node() {} 
    virtual ~MainViewTreeNode() {} 
    void bar() {} 
    void foo() { m_node.foo(); } 
protected: 
    T m_node; 
}; 

template <> 
void MainViewTreeNode<AreaNode>::bar() 
{ 
    printf("MainViewTreeNode<AreaNode>::bar\r\n"); 
} 

template <> 
void MainViewTreeNode<RoleNode>::bar() 
{ 
    printf("MainViewTreeNode<RoleNode>::bar\r\n"); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    MainViewTreeNode<RoleNode> role; 
    MainViewTreeNode<AreaNode> area; 

    std::list<ITreeNode*> nodes; 
    nodes.push_back(&role); 
    nodes.push_back(&area); 

    std::list<ITreeNode*>::iterator it = nodes.begin(); 

    for (; it != nodes.end(); ++it) 
    { 
     (*it)->foo(); 
     (*it)->bar(); 
    } 

    getchar(); 
    return 0; 
} 

Cảm ơn.

Trả lời

2

khách rất hữu ích khi bạn có nhiều hoạt độngvài loại.Nếu bạn có nhiều loại, nhưng ít hoạt động, hãy sử dụng tính đa hình bình thường.

+0

Ngay cả với một vài thao tác, mẫu Khách truy cập vẫn có thể cải thiện tách và mô đun của mã. – Thomas

+0

chỉ để được rõ ràng, bạn có nghĩa là có lẽ trong trường hợp này chỉ cần thực hiện các chức năng trực tiếp trong các lớp nút? – Mark

+0

@marksim: Vâng, đó là ý của tôi. OTOH, @Thomas không có điểm: Nó đan xen các hoạt động trên cây với dữ liệu của cây.Đưa dữ liệu và các hoạt động vào các đối tượng là những gì OOP là tất cả về, nhưng tôi không nghĩ rằng OOP là chén thánh. Nó có nhược điểm của nó. Cuối cùng, bạn là người duy nhất trong chúng tôi biết đủ về miền ứng dụng để có thể đưa ra quyết định sáng suốt. Chúng tôi chỉ có thể gợi ý các giải pháp khả thi. – sbi

1

Các sự cố như vậy, thật không may, tất cả đều quá phổ biến với ngôn ngữ C++ và ngôn ngữ OO được gõ tĩnh nói chung. Gần đây tôi đã tình cờ gặp this article mô tả cách triển khai công văn kép với một bảng tra cứu tùy chỉnh.

Tôi có thể thấy cách tiếp cận tương tự đang hoạt động tại đây. Về cơ bản, bạn xây dựng một bảng giấy gói chức năng của các loại Entry dưới đây:

class EntryBase { 
    public: 
     virtual bool matches(TreeNode const &node) const = 0; 
     virtual void operator()(TreeNode &node) const = 0; 
}; 

template<typename NodeType, typename Functor> 
class Entry : public EntryBase { 
    Functor d_func; 
    public: 
     Entry(Functor func) : d_func(func) { } 
     virtual bool matches(TreeNode const &node) const { 
      return dynamic_cast<NodeType const *>(&node) != 0; 
     } 
     virtual void operator()(TreeNode &node) const { 
      d_func(dynamic_cast<NodeType &>(node)); 
     } 
}; 

Mỗi bảng như vậy sau đó sẽ đại diện cho một loại khách (bạn có thể làm điều này mà không Boost quá, tất nhiên):

class NodeVisitor { 
    typedef boost::shared_ptr<EntryBase> EntryPtr; 
    typedef std::vector<EntryPtr> Table; 
    Table d_entries; 
    public: 
     template<typename NodeType, typename Functor> 
     void addEntry(Functor func) { 
      EntryPtr entry(new Entry<NodeType, Functor>(func)); 
      d_entries.push_back(entry); 
     } 
     void visit(TreeNode &node) { 
      EntryPtr entry = lookup(node); 
      if (!entry) 
       return; // this Visitor doesn't handle this type 
      (*entry)(node); 
     } 
    private: 
     EntryPtr lookup(TreeNode &node) { 
      Table::const_iterator iter = 
       std::find_if(d_entries.begin(), d_entries.end(), 
          boost::bind(&EntryBase::matches, _1, boost::ref(node))); 
      if (iter != d_entries.end()) 
       return *iter; 
      return 0; 
     } 
}; 

xây dựng một bảng sẽ là một cái gì đó như thế này:

void addToCompany(CompanyNode &company) { ... } 
void addToEmployee(EmployeeNode &employee) { ... } 

NodeVisitor nodeAdder; 
nodeAdder.addEntry<CompanyNode>(&addToCompany); 
nodeAdder.addEntry<EmployeeNode>(&addToEmployee); 

Sau khi tất cả các công việc đó, bạn chỉ có thể viết (không có bất kỳ bổ sung vào TreeNode o r bất kỳ lớp được thừa kế từ TreeNode):

nodeAdder.visit(someNode); 

Các mẫu đảm bảo rằng các dynamic_cast luôn thành công, vì vậy nó khá an toàn. Hạn chế lớn nhất là, tất nhiên, nó không phải là nhanh nhất trên thế giới. Nhưng để mở một hộp thoại, người dùng có lẽ là yếu tố chậm hơn, vì vậy nó phải đủ nhanh.

Tôi vừa mới triển khai khách truy cập này trong dự án của riêng tôi và nó hoạt động như một sự quyến rũ!

+0

@Thomas: Điều đó có vẻ thú vị, tôi nghĩ rằng tôi sẽ cần phải nghiên cứu nó một chút để có được đầu của tôi xung quanh nó. Trong thời gian chờ đợi, bạn nghĩ gì về chỉnh sửa đối với bài đăng đầu tiên của tôi ở trên? – Mark

+0

Tôi cũng sẽ cần nghiên cứu quá ... nhưng đã muộn và tôi quá mệt mỏi vì điều đó. Tôi sẽ cố gắng để có một cái nhìn vào ngày mai. Hoặc có lẽ một người nào đó có kinh nghiệm hơn sẽ đến trong thời gian chờ đợi. – Thomas

+0

@Thomas: Cảm ơn, vâng, đã muộn một chút cho tất cả điều này! Tôi rất cảm kích đầu vào. – Mark

1

Thay vì sử dụng m_node.foo(), những gì bạn cần làm là kế thừa tĩnh. Về cơ bản, đây là ý tưởng "template wrapper" của bạn, nhưng đó là một mẫu được thiết lập tốt.

class ITreeNode 
{ 
public: 
    virtual ~ITreeNode() {} 
    virtual void bar() = 0; 
    virtual void foo() = 0; 
}; 

template <class T> 
class MainViewTreeNode : public ITreeNode 
{ 
public: 
    MainViewTreeNode() : m_node() {} 
    virtual ~MainViewTreeNode() {} 
    void bar() {} 
    void foo() { m_node.foo(); } 
protected: 
    T m_node; 
}; 

trở thành

class ITreeNode 
{ 
public: 
    virtual ~ITreeNode() {} 
    virtual void bar() = 0; 
    virtual void foo() = 0; 
}; 

template <class T> 
class MainViewTreeNode : public ITreeNode 
{ 
public: 
    MainViewTreeNode() {} 
    virtual ~MainViewTreeNode() {} 
    void bar() { T::bar(); } 
    void foo() { T::foo(); } 
}; 
class RoleNode : public MainViewTreeNode<RoleNode> { 
    void bar() { std::cout << "Oh hai from RoleNode::bar()! \n"; } 
    void foo() { std::cout << "Oh hai from RoleNode::foo()! \n"; } 
}; 

Tất nhiên, nếu bạn đã có thừa kế thường xuyên trong hỗn hợp, tại sao không chỉ sử dụng? Sẽ không có giải pháp dễ dàng hơn so với sự đa hình bình thường ở đây. Nó hoạt động tốt khi số lượng các loại cao và số lượng hoạt động thấp. Có lẽ lỗ hổng trong thiết kế của bạn là bạn có bao nhiêu loại.

+0

Vấn đề là (mặc dù tha thứ cho tôi nếu tôi thiếu một cái gì đó), mã đó làm cho các lớp nút phụ thuộc vào MainViewTreeNode, và bây giờ việc thực hiện cả foo và bar nằm trong lớp nút. Những gì tôi đang cố gắng làm là giữ cho nút tự thực hiện "thanh" mà không sử dụng mẫu khách truy cập. Tuy nhiên, tôi thấy những gì bạn đã làm và đó là thực phẩm nhiều hơn cho suy nghĩ, cộng với tôi đưa vào bình luận khác của bạn về cách sử dụng đa hình thẳng và không lo lắng về de-khớp nối rất nhiều. Cảm ơn :-) – Mark

1

Một mẫu khác cần xem xét ở đây là Command pattern. Bạn làm cho các nút của bạn lưu trữ một danh sách các lệnh mà tất cả đều có GetName & Các phương thức thực hiện. Khi một nút được chọn, bạn liệt kê bộ sưu tập và gọi GetName trên mỗi lệnh để lấy tên của các mục menu và khi một mục menu được bấm, bạn gọi Execute. Điều này mang đến cho bạn sự linh hoạt tối đa, bạn có thể thiết lập các lệnh khi cây được tạo ra hoặc trong hàm tạo của từng nút. Dù bằng cách nào bạn cũng có thể sử dụng lại các lệnh theo loại và có số lượng lệnh khác nhau cho từng loại. Mặc dù vậy, kinh nghiệm của tôi sẽ gợi ý rằng cả mô hình này và khách truy cập có thể quá mức cần thiết trong trường hợp này và chỉ cần đặt các phương thức Add, Edit và Delete ảo vào loại nút gốc của cây là con đường để thực hiện. Các tính năng chính của chúng tôi là rất đơn giản.

+0

cảm ơn, một số thực phẩm tốt cho ý nghĩ ở đó. Tôi chắc chắn nghiêng về hướng đơn giản hơn bằng cách sử dụng thêm/chỉnh sửa/xóa/chọn trực tiếp trên các nút, hoặc ít nhất là trên một lớp mẫu có chứa nút cụ thể tương tự như ví dụ của tôi ở cuối bài viết của tôi. Không ai đã nói nó trông khủng khiếp, chưa ;-) – Mark

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