Tìm hiểu sâu về Ruby Modules 2
Bài đăng này đã không được cập nhật trong 3 năm
Nối tiếp phần trước (https://viblo.asia/march_vu/posts/jvElaLgYZkw), bài viết này tôi sẽ tìm hiểu kĩ về các phương thức Including, Prepending và Extending trong Ruby Object Model (ROM).
Including
Hãy bắt đầu mở class Car và include vào nó một Module:
module V12Engine
def start; "...roar!"; end
end
class Car
include V12Engine
end
Khi chúng ta include một module trong class, Ruby đặt nó trực tiếp phía trên class. Điều này có nghĩa rằng khi chúng ta gọi một phương thức từ object, Ruby sẽ tìm nó trong hệ thống object và chạy nó:
my_car.start
=> ...roar!"
Đây là cách thường lệ để kế thừa trong Ruby, chúng ta có thể include nhiều module trong class, để thêm nhiều chức năng bổ sung.
Chú ý: Nếu chúng ta định nghĩa một method có tên trùng với method trong class Car, Ruby sẽ tìm và chạy method đó thay thế, method trong Module sẽ không bao giờ được gọi.
Prepending
Khi chúng ta prepend một module cho class, Ruby đặt nó trực tiếp dưới class. Có nghĩa rằng, trong khi tra cứu method, Ruby sẽ đếm các method được định nghĩa trong Module trước các method được định nghĩa trong Class. Kết quả, bất kỳ các method của module sẽ thay thế các method trong class khi tên giống nhau. Hãy thử điều này:
module ElectricEngine
def drive; "eco-driving!"; end
end
class Car
prepend ElectricEngine
end
my_car = Car.new
my_car.drive
=> eco-driving!
Quan sát rằng nếu chúng ta gọi một method drive trong my_car, nó sẽ nhận method trong module, thay vì trong class Car. Prepending modules sẽ rất hữu dụng khi chúng ta muốn phạm vi logic method phù hợp với điều kiện biến đổi bên ngoài. Ví dụ, chúng ta muốn đánh giá sự thể hiện của method khi chạy trong môi trường test. Thay vì làm rối các method với lệnh if và code, chúng ta có thể thêm các method đặc biệt trong module riêng và chỉ thêm vào trước module tới class của chúng ta nếu chúng ta trong môi trường test:
module Instrumentable
require 'objspace'
def drive
puts ObjectSpace.count_objects[:FREE]
super
puts ObjectSpace.count_objects[:FREE]
end
end
class Car
prepend Instrumentable if ENV['RACK_ENV'] = 'test'
end
my_car = Car.new.drive
=> 711
=> driving
=> 678
Including và Prepending vào Instances
Các instance object được đặt phía ngoài hệ thống phân cấp ROM, không có ‘trên’ hay ‘dưới’ của chúng, nơi chúng ta có thể đặt các module. Điều này có nghĩa chúng ta không thể include hay prepend các module tới các object cụ thể.
Extending Classes
Khi chúng ta extend class object với một module, Ruby đặt nó trực tiếp phía trên eigenclass object của chúng ta:
module SuperCar
def fly; "flying!"; end
end
class Car
extend SuperCar
end
Các instances Car của chúng ta không thể truy cập đến method fly, phương thức tra cứu sẽ bắt đầu tại instance car, my_car, di chuyển bên phải tới eigenclass my_car và sau đó lên tới Car class và nó là nguồn gốc. method fly sẽ không bao giờ được tìm thấy trong đường dẫn đó:
my_car.fly
=> NoMethodError: undefined method `fly' for #<Car:0x000000019ae8d0>
Nhưng nếu ta gọi method trong class object thay thế?
Car.fly
=> flying
Object nhận của chúng ta bây giờ chính bản thân class Car. Đầu tiên Ruby sẽ nhìn bên phải (Car) và sau đó xem module SuperCar ở đâu, nó sẽ tìm thấy các phương thức fly.
Extending Instances
Chúng ta có thể extend một instance đặc biệt, như sau:
my_car.extend(SuperCar)
Việc này sẽ đặt module SuperCar phía trên eigen-class của object.
Bây giờ chúng ta có thể gọi method fly ở my_car:
my_car.fly
=> flying
Nhưng không gọi được ở another_car:
another_car.fly
=> NoMethodError: undefined method `fly' for #<Car:0x000000012ae868>
Extending một instance object với một Module là một cách tự động để decorate nhưng Class instances đặc biệt.
The end! Thank you!
Tài liệu dịch: https://www.sitepoint.com/get-the-low-down-on-ruby-modules/
All rights reserved