Tìm hiểu sâu về Ruby Modules 2

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, PrependingExtending 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/