Visitor Pattern trong C++
C++
30
Pattern
3
White

cpplover viết ngày 11/06/2015

Chức năng pattern matching trong C++ thấy nói sẽ kill visitor pattern.

Cách viết C++ do vậy sẽ thay đổi tương đối. Tự nhiên tôi thấy phải nhìn lại visitor pattern để xem vấn đề mà pattern này xử lý là gì.

Visitor Pattern

alt text

Theo như hình thì visitor pattern gồm 2 thành phần:

  • Giao diện: Visitor định nghĩa 1 hàm visit. Hàm này nhận Element.
  • Element: định nghĩa 1 giao diện có hàm accept. Hàm này nhận lại 1 visitor
  • 1 ConcreteElement: override hàm accept và xử lý theo cách riêng của đối tượng này.

Người dùng sẽ định nghĩa ConcreteVisitor và dùng đối tượng này để "visit" Element. Mỗi lement khi được duyệt này sẽ dùng phương pháp accept để chấp nhận visitor. Hàm accept này sẽ lại gọi ngược lại method visittheo nghĩa: mày đang visit tao đấy, ConcreteElement đấy. Do hàm visit có thể có các xử lý khác nhau, ta có thể lồng các xử lý này vào cùng 1 lần duyệt, dưới cùng một interface

Nói không có vẻ khó hiểu, ta sẽ xét ví dụ

Ví dụ

Cây nhị phân có 2 Node: Left và Right. Ta có 1 danh sách các Node của 1 cây nhị phân. Bài toán là cần đếm số lượng các Node theo thể loại.

Ví dụ:

Node* nodeList[] = {
    new Left, new Right, new Left, new Left,
    new Right,new Left, new Left, new Right,
    new Right, new Left, new Left, NULL
};

thì kết quả cần trả về là: Left: 7, Right: 4

Ta dùng Visitor Pattern để giải quyết bài toán như sau:


#include <iostream>

class Left;
class Right;

class Visitor {
public:
    virtual ~Visitor() {};
    virtual void Visit(Left *l) = 0;
    virtual void Visit(Right *r) = 0;
};

class Node {
public:
    virtual ~Node() {};
    virtual void Accept(Visitor* v) = 0;
};

class Left: public Node {
public:
    void Accept(Visitor* v) {
        v->Visit(this);
    }
};

class Right: public Node {
public:
    void Accept(Visitor* v) {
        v->Visit(this);
    }
};

class CountNode: public Visitor {
public:
    CountNode(): kLeftCount_(0), kRightCount_(0) {}

    void Visit(Left *l) {
        kLeftCount_++;
    }

    void Visit(Right *l) {
        kRightCount_++;
    }

    void Report() const {
        std::cout << "Left node: " << kLeftCount_ << std::endl
                  << "Right node: " << kRightCount_ << std::endl;
    }

private:
    int kLeftCount_;
    int kRightCount_;
};

int main(int argc, char **argv) {
    Node* nodeList[] = {
        new Left, new Right, new Left, new Left,
        new Right,new Left, new Left, new Right,
        new Right, new Left, new Left, NULL
    };
    CountNode visitor;
    visitor.Report();

    for (int i = 0; nodeList[i]; ++i) {
        nodeList[i]->Accept(&visitor);
    }

    visitor.Report();
    return 0;
}

Chương trình được biên dịch và chạy bởi câu lệnh sau:

$ clang++ visitor.cpp -o visitor
$ ./visitor
Left node: 0
Right node: 0
Left node: 7
Right node: 4

Đối tượng Visitor là một giao diện có 2 phương thức ứng với khi thăm node Left và Right. Do bài toán của ta là Count nên ta thừa kế Visitor và viết xử lý đếm node tương ứng với khi thăm node Left và Right. Do C++ có cơ chế dynamic dispatch nên sẽ dựa vào loại Object lúc chạy mà quyết định sẽ gọi hàm nào.

Các Node Left Right thừa kế phương pháp Accept của Node để chấp nhận Visitor.

Kết quả là ở main, chỉ cần 1 vòng For và lời gọi AcceptVisitor có được thông tin đầy đủ các các đối tượng trong danh sách nodeList.

Có thể thấy đây là Visitor Pattern cho phép Multiple Dispatch trong C++. Phương pháp visit nào được gọi được quyết định ở runtime, dựa vào kiểu của đối tượng element. Nhờ pattern này mà ta có thể phân biệt được 1 đối tượng là Left hay Right và đưa ra xử lý thích hợp cho đối tượng đó (Visitor).

Nếu Pattern Matching được đưa vào C++ 17, tôi nghĩ ta có thể dùng pattern matching để phân biệt xử lý, và do vậy không phải viết Visitor interface như hiện tại.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

cpplover

12 bài viết.
47 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
17 5
C++ 11 có chức năng lambda. Vì là ngôn ngữ "static type" nên C++ lambda function nhìn khó hơn lambda trong các ngôn ngữ dynamic type khác nhưng pyt...
cpplover viết 3 năm trước
17 5
White
16 2
Giới thiệu Cách duy nhất để học C++ trong 21 ngày là dành hơn 14611 ngày để nghiên cứu và thay thế chính bạn ở ngày số 21 (Ảnh) C++ là một ngôn ...
cpplover viết hơn 3 năm trước
16 2
White
16 0
Lang thang hacker news tìm được một phần nhỏ (Link) của một bác ở Anh tổng hợp về stack mà google đã tạo và sử dụng. Chi tiết về các stack được tổ...
cpplover viết hơn 3 năm trước
16 0
Bài viết liên quan
Male avatar
0 0
Constructor của lớp cha luôn được gọi trước constructor của lớp con. class Foo { public: Foo() { cout << "Base class initializing" << endl; ...
baoquocphan viết hơn 1 năm trước
0 0
White
18 0
Nhân tiện vừa đọc bài viết liên quan tới OpenCV trên Kipalog, nên em xin giới thiệu về giải thuật sinh ảnh mosaic từ một ảnh gốc. Không hiểu sinh ả...
Cùi Bắp viết hơn 2 năm trước
18 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
12 bài viết.
47 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!