# Design pattern trong Odoo: Template Method

> Template Method là gì và module delivery của Odoo áp dụng mẫu thiết kế này ra sao để thêm đơn vị vận chuyển mới mà không phải sửa code lõi.

**Source:** <https://trobz.com/vi/insights/design-patterns-odoo-template-method/>

---


Hãy thử hình dung việc quản lý vận chuyển trong Odoo khi logic của mọi đơn vị vận chuyển bị dồn vào một chỗ. Với mỗi thao tác (lấy giá cước, gửi hàng hay theo dõi đơn), code sẽ là một khối `if/elif/else` rất dài, và mỗi lần thêm đơn vị vận chuyển mới là phải sửa đoạn code đó, kèm nguy cơ làm hỏng các tích hợp đang chạy. Cách làm này khó mở rộng, dễ lỗi và khó bảo trì.

Đây là lúc mẫu thiết kế Template Method phát huy tác dụng. Bài viết này giới thiệu mẫu thiết kế đó và cách module delivery của Odoo áp dụng nó.

## Template Method là gì?

Template Method là một mẫu thiết kế hành vi (behavioral design pattern): lớp cơ sở định nghĩa khung của một thuật toán, còn các lớp con có thể ghi đè từng bước cụ thể mà không làm thay đổi cấu trúc tổng thể (xem [Refactoring.Guru](https://refactoring.guru/design-patterns/template-method)). Nói đơn giản, mẫu này đưa ra khuôn mẫu cho cách làm một việc, rồi để phần chi tiết được bổ sung sau. Lớp chính gọi một chuỗi phương thức, trong đó một số là trừu tượng hoặc có cài đặt mặc định, còn lớp con cung cấp phần cài đặt cụ thể cho các phương thức đó.

## Odoo áp dụng mẫu thiết kế này trong module delivery như thế nào

Odoo dùng model `delivery.carrier` làm khuôn mẫu. Model này chứa logic chính để xử lý việc gửi hàng. Khi cần thêm một đơn vị vận chuyển mới, không phải sửa module delivery lõi, mà tạo một module mới theo ba bước:

1. **Kế thừa lớp cơ sở.** Tạo model kế thừa từ `delivery.carrier` để đơn vị vận chuyển mới trở thành một phần của hệ thống giao hàng.
2. **Khai báo đơn vị vận chuyển.** Mở rộng các lựa chọn của field `delivery_type`. Field này đóng vai trò bộ điều phối, cho Odoo biết cần dùng nhóm phương thức nào cho từng đơn vị vận chuyển.
3. **Cài đặt các bước cụ thể.** Thêm các phương thức cần thiết theo quy ước đặt tên `<provider_name>_<method_name>`. Model `delivery.carrier` lõi sẽ gọi động các phương thức này dựa trên `delivery_type` đã chọn, nhờ hai hàm `hasattr` và `getattr` của Python.

Nhờ vậy, phương thức chính `send_shipping` trong `delivery.carrier` không chứa logic riêng của bất kỳ đơn vị vận chuyển nào. Phương thức này chỉ xem `delivery_type` của carrier rồi gọi phương thức tương ứng, như `dhl_send_shipping` hay `fedex_send_shipping`. Trong Odoo 17.0, phương thức này được định nghĩa trong module `stock_delivery`:

```python
def send_shipping(self, pickings):
    ''' Send the package to the service provider

    :param pickings: A recordset of pickings
    :return list: A list of dictionaries (one per picking) containing of the form::
                     { 'exact_price': price,
                       'tracking_number': number }
                       # TODO missing labels per package
                       # TODO missing currency
                       # TODO missing success, error, warnings
    '''
    self.ensure_one()
    if hasattr(self, '%s_send_shipping' % self.delivery_type):
        return getattr(self, '%s_send_shipping' % self.delivery_type)(pickings)
```

## Ví dụ thực tế trong OCA

Module [`delivery_cttexpress`](https://github.com/OCA/delivery-carrier/tree/17.0/delivery_cttexpress) của OCA cho thấy rõ cách mẫu thiết kế này vận hành. Giống phần lớn các module kết nối đơn vị vận chuyển, module này được tổ chức quanh hai file model:

- `cttexpress_request.py`: lớp giao tiếp giữa Odoo và API của đơn vị vận chuyển, ở đây là CTT Express SOAP API.
- `delivery_carrier.py`: kế thừa `delivery.carrier`, thêm `cttexpress` vào `delivery_type` và cài đặt các phương thức `cttexpress_*`, như `cttexpress_send_shipping`, `cttexpress_cancel_shipment` và `cttexpress_get_label`.

Cách tiếp cận này giữ code gọn gàng, dễ tách module và cho phép thêm đơn vị vận chuyển mới mà không cần động đến code lõi của Odoo.

