Post

Visitor Pattern trong Java

Visitor

Visitor Pattern là một mẫu thiết kế hành vi (Behavioral Design Pattern) cho phép tách rời các thao tác trên một đối tượng khỏi cấu trúc của đối tượng đó. Nó giúp bạn có thể thêm các hành vi mới vào một lớp mà không cần thay đổi chính lớp đó.

Visitor Pattern rất hữu ích khi:

  • Bạn muốn thực hiện các hoạt động khác nhau trên các đối tượng của một cấu trúc phức tạp.
  • Bạn cần mở rộng các thao tác mà không làm thay đổi cấu trúc lớp ban đầu (tuân thủ Open/Closed Principle trong SOLID).

Visitor Pattern gồm các thành phần chính:

  • Visitor Interface: Định nghĩa các phương thức xử lý cho từng loại đối tượng cụ thể.
  • Concrete Visitor: Hiện thực các phương thức của Visitor interface.
  • Element Interface: Định nghĩa phương thức accept() để cho phép Visitor truy cập.
  • Concrete Elements: Hiện thực phương thức accept() và gọi lại phương thức của Visitor.
  • Client: Gửi yêu cầu tới các phần tử và visitor.

1. Ưu Điểm

  • Dễ dàng mở rộng hành vi mới: Bạn có thể thêm các Visitor mới mà không cần thay đổi các lớp hiện tại.
  • Tách biệt logic xử lý: Các thao tác logic được tách ra khỏi cấu trúc đối tượng, giúp code dễ bảo trì và mở rộng.

2. Nhược Điểm

  • Phức tạp khi thêm loại phần tử mới: Nếu cần thêm lớp mới vào hệ thống, bạn phải chỉnh sửa tất cả các lớp Visitor.
  • Không phù hợp với hệ thống nhỏ: Với các hệ thống đơn giản, việc triển khai Visitor có thể quá mức cần thiết.

4. Khi nào nên sử dụng Visitor Pattern?

  • Khi hệ thống có cấu trúc phức tạp và nhiều đối tượng khác nhau cần áp dụng các hành vi giống nhau.
  • Khi bạn cần thêm hành vi mới mà không muốn làm thay đổi mã nguồn hiện có.

5. Ví dụ

Hãy cùng thực hiện một ví dụ cụ thể về Visitor Pattern để tính thuế cho các sản phẩm khác nhau: sách, thuốc và các mặt hàng tạp hóa.

Bước 1: Tạo Interface Visitor

1
2
3
4
5
interface Visitor {
    void visit(Book book);
    void visit(Medicine medicine);
    void visit(Grocery grocery);
}

Bước 2: Tạo Interface Element

1
2
3
interface Element {
    void accept(Visitor visitor);
}

Bước 3: Tạo Các Concrete Element

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class Book implements Element {
    private double price;
    private String title;

    public Book(double price, String title) {
        this.price = price;
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Medicine implements Element {
    private double price;
    private String name;

    public Medicine(double price, String name) {
        this.price = price;
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public String getName() {
        return name;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Grocery implements Element {
    private double price;
    private String item;

    public Grocery(double price, String item) {
        this.price = price;
        this.item = item;
    }

    public double getPrice() {
        return price;
    }

    public String getItem() {
        return item;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

Bước 4: Tạo Concrete Visitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TaxVisitor implements Visitor {

    @Override
    public void visit(Book book) {
        double tax = book.getPrice() * 0.1;
        System.out.println("Book: " + book.getTitle() + ", Tax: " + tax);
    }

    @Override
    public void visit(Medicine medicine) {
        double tax = medicine.getPrice() * 0.05;
        System.out.println("Medicine: " + medicine.getName() + ", Tax: " + tax);
    }

    @Override
    public void visit(Grocery grocery) {
        double tax = grocery.getPrice() * 0.0;
        System.out.println("Grocery: " + grocery.getItem() + ", Tax: " + tax);
    }
}

Bước 5: Tạo Client

1
2
3
4
5
6
7
8
9
10
11
12
13
public class VisitorPatternDemo {
    public static void main(String[] args) {
        Element book = new Book(100, "Clean Code");
        Element medicine = new Medicine(200, "Paracetamol");
        Element grocery = new Grocery(50, "Rice");

        Visitor taxVisitor = new TaxVisitor();

        book.accept(taxVisitor);
        medicine.accept(taxVisitor);
        grocery.accept(taxVisitor);
    }
}

Ví dụ trên cho ta thấy rằng:

  • Tách biệt logic tính thuế khỏi các lớp Book, Medicine, và Grocery.
  • Dễ mở rộng: Nếu muốn thêm loại thuế mới (ví dụ thuế môi trường), chỉ cần tạo một Visitor mới mà không thay đổi các lớp Element.
  • Mở rộng Visitor: Dễ dàng thêm các chức năng mới như in báo cáo, tính phí bảo hiểm, v.v.

Lời kết

Visitor Pattern là một công cụ mạnh mẽ trong thiết kế phần mềm, giúp tách rời logic xử lý khỏi cấu trúc đối tượng. Mặc dù việc thêm phần tử mới có thể phức tạp, nhưng lợi ích trong việc mở rộng hành vi và bảo trì mã nguồn rất đáng giá.

Khi làm việc với các hệ thống lớn, đặc biệt trong các ứng dụng tài chính, thương mại điện tử, Visitor Pattern sẽ phát huy tối đa sức mạnh của nó.

Bài viết mang tính chất “ghi chú, lưu trữ, chia sẻ và phi lợi nhuận”.
Nếu bạn thấy hữu ích, đừng quên chia sẻ với bạn bè và đồng nghiệp của mình nhé!

Happy coding! 😎 👍🏻 🚀 🔥

This post is licensed under CC BY 4.0 by the author.