ADDING FUNCTIONALITY TO RUBY CLASSES WITH DECORATORS
Bài đăng này đã không được cập nhật trong 7 năm
Khi sử dụng một api của bên thứ 3 đôi khi chúng ta cần bổ sung thêm chức năng cho nó. Do vì nó là đối tượng đã được đóng gói nên không thể thêm chức năng bằng cách can thiệp vào bên trong nó. Chúng ta có thể không cần làm vậy mà đơn giản chỉ cần viết một đối tượng khác thực hiện các chức năng mở rộng mà ta đang cần thêm. Nhưng khi những chức năng mở rộng này có liên quan đến các chức năng của api hoặc được thực hiện đồng thời thì việc tách thành 2 đối tượng riêng biệt là rất tối nghĩa và phân tán. Trường hợp này chúng ra cần phải tạo ra một lớp bao hàm các chức năng của api và bổ sung thêm các chức năng mở rộng đang cần. Cần thực hiện việc này một cách tối ưu nhất để khi sử dụng sẽ thuận tiện và dễ hiểu.
Vấn đề đặt ra trong ví dụ cụ thể. Có một api của bên thứ 3 là Stripe(Stripe gem). Api này giúp thanh toán hóa đơn của khách hàng thồng qua phương thức thanh toán là thẻ visa hay mastercard. Chỉ thanh toán không là không đủ khi ta muốn lưu trữ thông tin khách hàng hay hóa đơn mỗi khi thanh toán được thực hiện. Chúng ta cần tạo ra một lớp có thể truy cập dữ liệu của Stripe và mở rộng thêm chức năng lưu trữ thông tin mà vẫn giữ được sự rõ ràng và xuyên suốt.
Giải pháp. Hãy bắt đầu với cách cơ bản nhất để truy cập vào dữ liệu Stripe: Stripe gem:
class AccountsController < ApplicationController
before_action :require_authentication
def show
@customer = Stripe::Customer.retrieve(current_user.stripe_id)
@invoices = @customer.invoices
@upcoming_invoice = @customer.upcoming_invoice
end
end
Extract an Adapter Bởi vì chúng ta đang kết nối với hệ thống của bên thứ 3 nên cần thiết tạo ra một local adapter để truy cập vào các phương thức của Stripe, đối tượng này đóng vai trờ thay thế đối tượng stripe để sử dụng trong những nơi cần thiết. Điều này tương đương với việc loại bỏ sự tồn tại của khái niệm Stripe mà thay thê bằng cái gọi là Billing. Đây là các hàm cần thiết của Billing để sử dụng cho AccountsController.
class Billing
attr_reader :billing_id
def initialize(billing_id)
@billing_id = billing_id
end
def customer
Stripe::Customer.retrieve(billing_id)
end
def invoices
customer.invoices
end
def upcoming_invoice
customer.upcoming_invoice
end
end
Lớp Billing chứa các phương thức để sử dụng trong các trường hợp mà không làm thay đổi chức năng của Stripe một cách có tổ chức hơn, rõ ràng hơn. Và bây giờ thử dùng lớp Billing này trong controller.
class AccountsController < ApplicationController
before_action :require_authentication
def show
billing = Billing.new(current_user.stripe_id)
@customer = billing.customer
@invoices = billing.invoices
@upcoming_invoice = billing.upcoming_invoice
end
end
Như vậy là chúng ta đã cung cấp các chức năng có sẵn thông qua một lớp trung gian giữa controller và Stripe. Việc làm này hoàn toàn là hợp lý và đúng nghĩa.
Thêm chức năng cho Billing Sau khi đã setup được adapter là lớp Billing, chúng ta sẽ tiến hành cải thiện hiệu suất việc lưu trữ các hành vi của người dùng. Như vậy cần tạo ra một lớp mới có tên BillingWithCache. Lớp này đại diện cho một đối tượng mới có thêm chức năng mới phục vụ chức năng ở một mức cao hơn, được đóng gói cao hơn dưới cách nhìn của người sử dụng. Cách cơ bản để sử dụng decorator là đưa vào đối tượng mà chúng ta đang decorating, ở đây là Billing, và khai báo các phương thức giống với lớp billing nhưng thông qua đối tượng thực hiện được khai báo ở đầu của lớp. Code của lớp BillingWithCache .
class BillingWithCache
def initialize(billing_service)
@billing_service = billing_service
end
def customer
billing_service.customer
end
def invoices
customer.invoices
end
def upcoming_invoice
customer.upcoming_invoice
end
private
attr_reader :billing_service
end
Ở đây mặc dù chúng ta chưa có thêm chức năng mở rộng nào, lớp này chỉ có khả năng gọi các phương thức của lớp Billing và nó trả về các kết quả của Stripe API (#customer, #invoices, #upcoming_invoice). Tích hợp lớp mới này vào AccountsController :
class AccountsController < ApplicationController
before_action :require_authentication
def show
billing = BillingWithCache.new(Billing.new(current_user.stripe_id))
@customer = billing.customer
@invoices = billing.invoices
@upcoming_invoice = billing.upcoming_invoice
end
end
Như bạn thấy ta chỉ thay đổi dòng code mà chúng ta cần sử dụng lớp decorated class.
BillingWithCache.new(Billing.new(current_user.stripe_id))
Chắc bạn đang đặt ra câu hỏi là không hề liên quan gì đến việc lưu trữ dữ liệu, chức năng vẫn vậy mà chứ chưa có gì được mở rộng. Nào hãy cùng đào sâu vào lớp BillingWithCache đề thêm điều đó. Adding Caching Functionality Để cache dữ liệu sử dụng Rails.cache, chúng ta cần một cache một key duy nhất. May mắn là lớp Billing cung cấp cho người dùng billing_id nó cho phép tạo ra sự truy cập duy nhất tới người dùng.
def cache_key(item)
"user/#{billing_id}/billing/#{item}"
end
Trường hợp này là tham chiếu đến "customer", "invoices" hoặc "upcoming_invoice". Thêm vào các lời gọi để cache key.
class BillingWithCache
def initialize(billing_service)
@billing_service = billing_service
end
def customer
key = cache_key("customer")
Rails.cache.fetch(key, expires: 15.minutes) do
billing_service.customer
end
end
def invoices
key = cache_key("invoices")
Rails.cache.fetch(key, expires: 15.minutes) do
customer.invoices
end
end
def upcoming_invoice
key = cache_key("upcoming_invoice")
Rails.cache.fetch(key, expires: 15.minutes) do
customer.upcoming_invoice
end
end
private
attr_reader :billing_service
def cache_key(item)
"user/#{billing_service.billing_id}/billing/#{item}"
end
end
Đoạn code trên cache mỗi 15 phút. Trên đây là một cách wrapped đối tượng của bên thứ 3 và bổ sung thêm chức năng một cách đơn giản nhưng cũng hữu hiệu.
All rights reserved