Không nghi ngờ gì nữa, các mô-đun và mixin là những nguồn tài nguyên tuyệt vời khiến Ruby trở nên hấp dẫn. Họ cung cấp cho ứng dụng khả năng chia sẻ mã có thể được sử dụng dễ dàng ở những nơi khác. Nó cũng giúp chúng tôi tổ chức mã của mình bằng cách nhóm các chức năng và mối quan tâm, điều này giúp cải thiện khả năng đọc và khả năng bảo trì của mã của chúng tôi.
Trong bài viết này, chúng ta sẽ đi qua các khái niệm đằng sau mô-đun và mixin. Chúng ta sẽ tìm hiểu cách tạo và kết hợp các mô-đun vào các lớp khác và thảo luận về lợi ích của việc sử dụng chúng trong ứng dụng Ruby on Rails.
Tôi hy vọng bạn sẽ thích cuộc hành trình!
Mô-đun là gì
Các mô-đun là một trong những tài nguyên sáng giá nhất của Ruby vì chúng mang lại hai lợi ích tuyệt vời:chúng ta có thể tạo không gian tên để ngăn chặn xung đột tên và chúng ta có thể sử dụng chúng làm hỗn hợp để chia sẻ mã trên toàn ứng dụng.
Về mặt cấu trúc, một mô-đun khá giống với bất kỳ lớp Ruby nào. Trên thực tế, đối với Ruby, một Class
là một Module
, như chúng ta có thể thấy bên dưới trong irb
bảng điều khiển:
> Class.is_a?(Module)
=> true
Tương tự như các lớp, với các mô-đun, chúng tôi cũng nhóm các phương thức và hằng số và chia sẻ mã. Tuy nhiên, có một vài điểm khác biệt giữa mô-đun và một lớp Ruby thuần túy:
- Chúng tôi bắt đầu định nghĩa với mô-đun
module
từ khóa thay vìclass
; - Chúng tôi không thể khởi tạo mô-đun, vì vậy không thể tạo đối tượng nào từ nó;
- Chúng tôi không thể kế thừa từ các mô-đun, vì vậy thay vào đó chúng tôi sử dụng chúng làm tổng hợp;
- Mô-đun là mã độc lập, vì vậy không có hệ thống phân cấp kế thừa của mô-đun;
Các mô-đun là nơi tuyệt vời để có các dịch vụ, mối quan tâm, hằng số và bất kỳ mã nào khác, bằng cách có trách nhiệm giống nhau, chúng nên ở cùng nhau.
Đây là cách một mô-đun sẽ trông như thế nào:
# lib/modules/invoice_creator.rb
module InvoiceCreator
TAX_FEE = 0.5
def self.generate
puts "Don't worry! I'll generate the invoice for you at #{TAX_FEE}%"
end
def invoice_total
puts "I'll return the invoice total"
1000
end
end
Trong ví dụ này, chúng ta có thể thấy rằng một mô-đun có thể cung cấp hai loại phương thức:phương thức mô-đun và phương thức phiên bản.
self.generate
là một phương thức mô-đun, có nghĩa là chúng ta có thể sử dụng nó mà không cần phải bao gồm (hoặc mở rộng) mô-đun trong bất kỳ đối tượng nào khác. Ví dụ, điều này rất phổ biến khi chúng ta đang tạo các đối tượng dịch vụ. Chúng ta có thể gọi phương thức mô-đun của mình như sau:
2.5.3 :006 > InvoiceCreator.generate
Don't worry! I'll generate the invoice for you
=> nil
invoice_total
là một phương thức thể hiện và để có thể sử dụng nó, chúng ta cần đưa mô-đun vào một lớp, như sau:
# app/models/invoice.rb
class Invoice < ApplicationRecord
include InvoiceCreator # This includes our module in the Invoice class
def calculate_tax
total = invoice_total # included method from our module
tax = total * InvoiceCreator::TAX_FEE
puts "This is the invoice tax: #{tax}"
end
end
Tất cả các phương thức phiên bản từ InvoiceCreator
có sẵn cho Invoice
các phiên bản, vì vậy chúng tôi có thể gọi calculate_tax
phương pháp khá dễ dàng:
2.5.3 :008 > Invoice.new.calculate_tax
"I'll return the invoice total"
"This is the invoice tax: 500.0"
=> nil
2.5.3 :009 >
Ngoài ra, bên trong calculate_tax
bạn nhận thấy rằng chúng tôi đang sử dụng một hằng số đã được xác định bên trong mô-đun của chúng tôi. Như tôi đã đề cập trước đây, các mô-đun là những người giữ cố định tuyệt vời!
Bây giờ hãy tưởng tượng một tình huống mà chúng ta cần có hai loại InvoiceCreator
để tạo các hóa đơn hoàn toàn khác nhau cho nhà cung cấp và khách hàng. Cuối cùng chúng ta sẽ có một phân loại tên, và để tránh điều này, chúng ta sử dụng lợi ích tuyệt vời khác của mô-đun:không gian tên. Chúng ta hãy xem trong phần tiếp theo.
Không gian tên mọi nơi
Không gian tên có thể được định nghĩa là một cách để tổ chức mã của chúng tôi khi chúng tôi muốn tạo ngữ cảnh cục bộ cho một chức năng nhất định, đó là những gì chúng tôi cần cho tình huống tôi vừa mô tả:một ngữ cảnh khác cho người tạo hóa đơn của khách hàng và một ngữ cảnh khác cho nhà cung cấp người tạo hóa đơn.
Hãy quay lại mã của chúng tôi. Chúng tôi sẽ cần tạo hai mô-đun khác nhau:
# lib/modules/customer/invoice_creator.rb
module Customer
module InvoiceCreator
def self.generate
puts "Don't worry! I'll generate the customer invoice for you"
end
end
end
# lib/modules/supplier/invoice_creator.rb
module Supplier
module InvoiceCreator
def self.generate
puts "Don't worry! I'll generate the supplier invoice for you"
end
end
end
Sau đó, chúng tôi có thể sử dụng Customer::InvoiceCreator
hoặc Supplier::InvoiceCreator
nơi chúng tôi cần nó:
2.5.3 :014 > Customer::InvoiceCreator.generate
Don't worry! I'll generate the customer invoice for you
=> nil
2.5.3 :015 > Supplier::InvoiceCreator.generate
Don't worry! I'll generate the supplier invoice for you
=> nil
2.5.3 :016 >
Bằng cách đó, mỗi mã cụ thể sẽ được bao bọc bên trong mô-đun riêng của nó, tôn trọng nguyên tắc phân tách các mối quan tâm. Bên cạnh đó, không gian tên ở khắp mọi nơi cũng là một cách tuyệt vời để giữ cho mã của chúng ta được tổ chức tốt.
Bây giờ chúng ta hãy xem cách chúng ta có thể sử dụng những lợi ích khác của mô-đun Ruby:mixins.
Sự kỳ diệu của việc sử dụng Mixin
Như bạn có thể đã biết, một đặc điểm của Ruby là nó thực hiện cơ chế kế thừa đơn, có nghĩa là một lớp chỉ có thể kế thừa từ một lớp khác. Chúng ta thường có thể cần kế thừa từ nhiều lớp hơn. Trong Ruby, chúng ta có thể đáp ứng nhu cầu đó bằng cách sử dụng thành phần trên mẫu kế thừa.
Điều này có thể thực hiện được bằng cách sử dụng các mixin. Khi chúng ta trộn một đoạn mã trong một lớp Ruby khác, chúng ta đang thêm vào lớp này nhiều hành vi hơn mà không cần sử dụng kế thừa và điều này thật tuyệt vời. Do đó, trong Ruby, mixin là các mô-đun mà chúng tôi đưa vào các lớp khi chúng cần thiết. Với điều đó, chúng tôi có được bằng cách giữ cho mã của chúng tôi sạch sẽ và các trách nhiệm được tách biệt, như chúng phải như vậy.
Ví dụ:giả sử rằng InvoiceCreator
của chúng tôi mô-đun là một dịch vụ cần sử dụng các chức năng được cung cấp bởi một số mô-đun khác, chẳng hạn như InvoiceCalculator
, InvoiceRenderer
và InvoiceSender
, chúng sẽ cần thiết để thực hiện quy trình tạo hóa đơn.
Chúng tôi có thể đạt được điều này bằng cách bao gồm một chuỗi các mô-đun làm mixin trong mã của chúng tôi, vì vậy chúng tôi có thể sử dụng các phương pháp trực tiếp, như ví dụ bên dưới:
# lib/modules/invoice_calculator.rb
module InvoiceCalculator
def calculate_items_total
puts "imagine some math here"
end
end
# lib/modules/invoice_renderer.rb
module InvoiceRenderer
def generate_invoice_pdf
puts "imagine that we are using some PDF generation magic here"
end
end
# lib/modules/invoice_sender.rb
module InvoiceSender
def send_invoice
puts "imagine your favorite mail service being used here"
end
end
Vì chúng tôi không thể tạo InvoiceCreator
kế thừa từ tất cả ba mô-đun khác, thay vào đó chúng tôi sẽ bao gồm chúng. Bằng cách này, InvoiceCreator
bao gồm tất cả các phương thức từ các mô-đun Hóa đơn khác và chúng ta có thể gọi các phương thức này trong bất kỳ lớp / mô-đun nào là InvoiceCreator
mô-đun được bao gồm trong. Hãy cẩn thận! Nếu bất kỳ mô-đun nào có các phương thức trùng tên, chúng sẽ ghi đè lẫn nhau.
# lib/modules/customer/invoice_creator.rb
module Customer
module InvoiceCreator
include InvoiceCalculator
include InvoiceRenderer
include InvoiceSender
def generate_invoice
calculate_items_total # from InvoiceCalculator
generate_invoice_pdf # from InvoiceRenderer
send_invoice # from InvoiceSender
end
end
end
Giờ đây, chúng tôi có thể gọi các phương thức dịch vụ của mình ở bất kỳ đâu mà chúng tôi đưa vào bằng cách thực hiện như sau:
# app/models/invoice.rb
class Invoice < ApplicationRecord
include Customer::InvoiceCreator
def send_invoice_to_customer
puts "Don't worry! I'll generate the customer invoice for you"
generate_invoice
end
end
Đây sẽ là kết quả, gọi các phương thức từ các mô-đun Hóa đơn đi kèm thông qua InvoiceCreator
mô-đun:
2.5.3 :051 > Invoice.new.generate_invoice
Don't worry! I'll generate the supplier invoice for you
imagine some math here
imagine that we are using some PDF generation magic here
imagine your favorite mail service being used here
=> nil
Lưu ý rằng đây là cách chúng tôi sử dụng thành phần thay vì nguyên tắc kế thừa. Nguyên tắc này cho thấy bạn nên sử dụng bố cục bất cứ khi nào bạn có thể. Trong thành phần, chúng tôi tạo các lớp có trách nhiệm cung cấp các chức năng cụ thể cho những người khác, đó chính xác là những gì chúng tôi đang làm.
Đó là tất cả mọi người!
Là các nhà phát triển Ruby, chúng tôi thích sử dụng các cơ sở và cú pháp của nó khi lập kế hoạch, viết, duy trì và cấu trúc lại mã của chúng tôi. Tôi hy vọng rằng thông qua bài viết này, bạn có thể đánh giá cao lý do tại sao mô-đun là một nguồn tài nguyên tuyệt vời để giúp cải thiện khả năng đọc mã của chúng tôi, để giữ mọi thứ chỉ có một trách nhiệm và giữ cho cơ sở mã của chúng tôi sạch sẽ và dễ bảo trì. Và chúng tôi cũng có thể sử dụng phép thuật mixins để đưa các mô-đun vào mã của chúng tôi trong những trường hợp chúng tôi cần kế thừa từ nhiều nguồn.
Chúng ta sẽ tiếp tục nói về những tài nguyên lớn nhất của Ruby và Rails ở đây, vì vậy hãy chú ý theo dõi!
Tái bút. Nếu bạn muốn đọc các bài đăng của Ruby Magic ngay khi chúng xuất hiện trên báo chí, hãy đăng ký bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ một bài đăng nào!