Builder pattern for you

- 6 mins

Introduction

Code của bạn bằng một cách nào đó cần phải có tính linh hoạt, dễ bảo trì sau này, có khả năng tái sử dụng. Đó là khi design pattern cần được sử dụng đến. Lợi ích của design pattern:

Builder pattern

Đây là một pattern khá phổ biến từ người mới biết đến code cho đến những senior lâu năm đều có thể gặp hoặc đang sử dụng. Pattern này rất linh hoạt khi cần tổ chức các đối tượng phức tạp, nhiều thuộc tính, mỗi thuộc tính lại có nhiều lựa chọn.

Ví dụ: một đối tượng ở đây là mèo. Một con mèo thì có thể được biểu diễn bởi nhiều thuộc tính:

Một ví dụ khác với câu lệnh SQL. Ta có câu lệnh đơn giản như sau: SELECT ... FROM ... WHERE

Sau mỗi mệnh đề select ta có thể truyền tên trường cụ thể mà ta muốn select hoặc select *, count, cast… Tương tự sau mệnh đề from có thể là tên bảng bất kì,…

Builder pattern sẽ giúp biểu diễn một con mèo phức tạp ở trên bằng code một cách logic, tạo các thuộc tính của mèo nhanh chóng và linh hoạt.

SQL queries

Bây giờ ta sẽ áp dụng builder pattern vào thực tế bằng cách tạo câu lệnh sql. Như đã nói ở trên, trong một câu lệnh sql có nhiều thành phần giống nhau mà câu lệnh nào ta cũng gặp phải như các mệnh đề select, from, where, having… Việc viết những câu lệnh như vậy nhiều lần xảy ra vấn đề:

Mình sẽ ví dụ một class để tạo câu lệnh sql sử dụng builder pattern. Tất nhiên đối với các câu lệnh đơn giản như SELECT * FROM db.table thì viết tay chắc sẽ nhanh hơn, nhưng bạn thử dụng với những câu lệnh dài hơn xem.

class QueryBuilder:

    def __init__(self):
        """
        Khởi tạo một class QueryBuilder gồm các thuộc tính là các mệnh đề:
        - select_clause
        - from_clause
        - where_clause
        - groupby_clause

        Khi sử dụng, bạn có thể bổ sung thêm các thuộc tính là các mệnh đề khác mà bạn cần trong câu truy vấn
        """
        self.select_clause = ""
        self.from_clause = ""
        self.where_clause = ""
        self.groupby_clause = ""
        # self.having_clause = ""
        # self.join_clause = ""
        # self.update_clause = ""
        # self.insert_clause = ""
        # ...

    def select_builder(self, clause_value: str):
        """
        Method này truyền vào đoạn truy vấn select
        :param clause_value: đoạn truy vấn select mà bạn muốn
                clause_value: str để ràng buộc giá trị truyền vào của method này là kiểu string
        :return: self
        """
        self.select_clause = clause_value
        return self

    def from_builder(self, clause_value: str):
        self.from_clause = clause_value
        return self

    def where_builder(self, clause_value: str):
        self.where_clause = clause_value
        return self

    def groupby_builder(self, clause_value: str):
        self.groupby_clause = clause_value
        return self

    def build_select(self):
        query = None

        # Kiểm tra xem mệnh đề select, from có trống không, nếu có thì trả về lỗi cho chương trình
        if self.select_clause == "" or self.select_clause is None:
            return query

        if self.from_clause == "" or self.from_clause is None:
            return query

        # Ta có thể các đoạn lệnh để kiếm tra điều kiện của từng mệnh đề trong các method clause_builder

        if self.where_clause != "" and self.where_clause is not None:
            where_clause = f"WHERE {self.where_clause}"
        else:
            where_clause = ""

        if self.groupby_clause != "" and self.groupby_clause is not None:
            groupby_clause = f"GROUPBY {self.groupby_clause}"
        else:
            groupby_clause = ""

        # Ghép các mệnh đề thành câu truy vấn hoàn chỉnh
        query = f"SELECT {self.select_clause} " \
                f"FROM {self.from_clause} " \
                f"{where_clause} " \
                f"{groupby_clause};"
        return query

Ở đây mình viết một class để build câu query giả sử có các mệnh đề select, from, where, groupby. Bạn có thể bổ sung thêm các mệnh đề khác theo nhu cầu. Method build_select() dùng để build câu lệnh select hoàn chỉnh.

Bây giờ ta thử build một câu truy vấn đơn giản từ class vừa viết như sau:

query_builder = QueryBuilder()
query_builder.select_builder("catName, catColor")\
    .from_builder("Cats")\
    .where_builder("catAge < 10")\
    .groupby_builder("catColor")

query = query_builder.build_select()
print("SQL query: " + query)

Câu truy vấn in ra như sau: SQL query: SELECT catName, catColor FROM Cats WHERE catAge < 10 GROUPBY catColor;

Có một kĩ thuật nhỏ là mỗi method build mệnh đề select, from, where, groupby mình đều trả self nghĩa là trả về chính đối tượng QueryBuilder mà ta đang xây dựng. Cách này dùng khi bạn muốn build câu lệnh sử dụng pipeline, tương tự cách sử dụng các hàm trong nhiều thư viện như scikit-learn, spark.

query_builder.select_builder("catName, catColor")\
    .from_builder("Cats")\
    .where_builder("catAge < 10")\
    .groupby_builder("catColor")

Trong trường bạn câu lệnh của bạn không có mệnh đề where hay groupby thì bạn chỉ cần bỏ qua hoặc truyền vào giá trị None hoặc string rỗng "" thì method builder_select sẽ bỏ qua mệnh đề này. Ngoài ra bạn có thể tự viết thêm các method để build những câu query khác như build_update(), build_insert(), build_delete()

Bây giờ, khi sử dụng builder pattern để tạo ra một câu truy vấn sql sẽ chỉ cần viết ra một file riêng và import vào bất kì notebooks nào khác mà bạn muốn sử dụng. Code sẽ trở nên dễ đọc, trực quan hơn, và dễ debug khi câu lệnh sql có lỗi cú pháp (syntax).

Thanks!

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora