12/08/2018, 16:08

Hook method trong ruby

Hook method là những phương thức đặc biệt của Ruy cung cấp một cách mở rộng hoạt động của chương trình tại thời điểm runtime Hook method giống như việc đăng kí một sự kiện và sự kiện đó sẽ được callback bởi một chỗ khác Khi trình biên dịch đọc đến dòng code mà thấy có sự kế thừa thì nó sẽ ...

  • Hook method là những phương thức đặc biệt của Ruy cung cấp một cách mở rộng hoạt động của chương trình tại thời điểm runtime
  • Hook method giống như việc đăng kí một sự kiện và sự kiện đó sẽ được callback bởi một chỗ khác
  • Khi trình biên dịch đọc đến dòng code mà thấy có sự kế thừa thì nó sẽ callback đến một hàm của class cha, hàm đó chính là một hook method
  • Chúng ta sẽ tìm hiểu về các hook methods sau: inherited included extended prepended method_missing

Inherited

Inherited là một mối quan hệ giữa các class. Chúng ta biết rằng mèo là một loại động vật có vú và động vật có vú lại là động vật. Lời ích của việc thừa kế là các class mức thấp hơn trong cây thừa kế sẽ có được những đặc điểm của các class cao hơn trong dãy thừa kế không chỉ vậy nó còn có thể thềm những đặc điểm riêng biệt của nó. Nếu tất cả động vật có vú có thể thở thì mèo cũng có thể thở. Trong ruby một class chỉ có thể thừa kế từ 1 class khác. Nhiều ngôn ngữ khác support việc đa thừa kế, đặc điểm này cho phép class có thể thừa kế từ nhiều class khác nhau nhưng trong ruby thì không support điều này.

class Mammal  
  def breathe  
    puts "inhale and exhale"  
  end  
end  
  
class Cat < Mammal  
  def speak  
    puts "Meow"  
  end  
end  
  
rani = Cat.new  
rani.breathe  
rani.speak  

Mặc dù chúng ta không có hàm cụ thể rằng mèo có thể thở, mỗi con mèo sẽ thừa kế các hành vi kia từ class Mammal vì vậy Cat sẽ được định nghĩa như một subclass của Mammal. Vì vậy mà từ một quan điểm của các lập trình viên, mèo có khả năng thở và sau đó chúng ta thêm phương thức nói nữa thì mèo vừa có 2 đặc điểm thở và nói.

Có những trường hợp mà ở đó những thuộc tính của supper-class sẽ không được thừa kế bời một subclass cụ thể. Mặc dù chim là hoàn toàn biết bay nhưng chim cánh cụt là không thể bay được. Các bạn tham khảo ví dụ sau.

class Bird  
  def preen  
    puts "I am cleaning my feathers."  
  end  
  def fly  
    puts "I am flying."  
  end  
end  
  
class Penguin < Bird  
  def fly  
    puts "Sorry. I'd rather swim."  
  end  
end  
  
p = Penguin.new  
p.preen  
p.fly

Inheritance cho phép bạn tạo một class với những phương thức được sử dụng lại hoặc được chuyển hòa từ phương thức của các class khác vì vậy mà class được tạo ra sẽ gọn gàng hơn. Một class chỉ có thể thừa kế từ một class tại một thời điểm nhất định.

Included

  • include mang tất cả những method của module về làm instance method của class
  • Tương tự inherite từ một class ta include một module này khi trình biên dịch đọc đến kí tự include thì nó sẽ callback tới method self.incluđe của module cha.
module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!

Trong đó class Bar đã include module Foo và sử dụng các method của Foo nhưng những instance method

Extended

  • Khi một class include một module thì các method trong module sẽ trở thành các class method của class đó
class Bar
	extend Foo
end

Bar.foo # heyyyyoooo!

Trong đó class Bar extend module Foo và sử dụng các method của module Foo như những class method của mình

Prepended

Cách tốt nhất để hiểu được Module#prepend làm việc như thế nào chúng ta sẽ tìm ra các mà nó sử lý trong ruby Chúng ta bắt đầu với ví dụ sau

class Human
  def hi
    "Hi from Human!"
  end
end

class Man < Human
end

Man.new.hi  # => Hi from Human!

Đó là một cách dễ nhất vào rõ ràng nhất về thừa kế mà ai cũng biết. Method trong class cha đã được gọi đến

Tiếp theo hãy xem trường hợp thú vị sau khi mà method có cùng tên trong cả class và module cha.

class Human
  def hi
    "Hi from Human!"
  end
end

module Greetable
  def hi
    "Hi from Module!"
  end
end

class Man < Human
  include Greetable
end

Man.new.hi  # => "Hi from Module!"

Như chúng ta đã thấy, trong trường hợp này phương thức trong module đã được gọi đến. Vậy trong cây thừa kế, module có độ ưu tiên cao hơn class cha. Chúng ta có thể sử dụng method ancestor để kiểm tra:

Man.ancestors # => [Man, Greetable, Human, ...]

Điều đó chỉ ra rằng để tìm một method, Ruby sẽ tìm trong class Man trước sau đó đến module Greetable rồi đến Human class.

Thôi chúng ta quay lại với Module#presend

Chúng ta sẽ kiểm tra xem điều gì sẽ xảy ra nếu chúng ta thay đổi inlcude Greetable to presend Greetable Phương thức ancestors chỉ ra sự khác sau khi thay đổi include thành prepend

Man.ancestors # => [Greetable, Man, Human, ...]

Như chúng ta nhìn thấy Greetable là phần tử đầu tiền trong chuỗi các tổ tiền của Man. Nó có nghĩa rằng thậm chí chúng ta thêm method hi trong class Man thì nó cũng sẽ không gọi nó.

module Greetable
  def hi
    "Hi from Module!"
  end
end

class Man
  prepend Greetable

  def hi
    "Hi from Man"
  end
end

Man.new.hi  # => "Hi from Module!"

Man.ancestors.inspect # => [Greetable, Man, Object]

Việc này làm thay đổi cách tìm kiếm hàm trong ruby vậy nên bạn nên cẩn thận khi sử dụng nó

Method_missing

Kernel#method_missing() được gọi khi Ruby gửi một thông điệp và người nhận không tìm ra phương thức để trả về. Kernel là một module được đưa vào Object và hầu như tất cả các Ruby objects đều được thừa kế từ Object vì vậy tất cả các Ruby methods sẽ trả về method_missing. Method_missing có thể được ghi đè để thực thi các hành vi theo ý muốn của lập trình viên.

class Dude
  def life_outlook
    'cowabunga'
  end
 
  def method_missing(name, *args)
    'radical haircut'
  end
end
 
d = Dude.new
p d.undefined_method # => 'radical haircut'
p d.life_outlook # => 'cowabunga'

Khi một thực thể của class Dude sử một thông điệp mà nó có thể trả về được thì nó vẫn trả về bình thường. Nhưng nếu một thực thể của class Dude gửi một thông điệp mà nó không thể thực trả về được thông điệp đó thì nó sẽ gọi đến medthod_missing và trả về là 'radical haircut'. Thật là nột ý tưởng tồi để ghi đè method_missing cho tất cả các thông điệp. Vậy hãy refactor đoạn code trên để chỉ overwrite method_missing cho các thông điệp sau: :haircut, :flow, and :dome_style.

class Dude
  def method_missing(name, *args)
    return super unless [:haircut, :flow, :dome_style].include?(name)
    'radical haircut'
  end
end
 
p Dude.new.undefined_method # => NoMethodError: undefined method `undefined_method'
p Dude.new.haircut # => 'radical haircut'

Các phương thức methods() và respond_to?() sé không đăng kí các đối tượng thông điệp mới có thể sử lý khi message_missing được customized.

class A
  def method_missing(name, *args)
    return "hi" if name == :hello
  end
end
 
a = A.new
p a.hello # => 'hi'
a.respond_to? :hello # => false
a.methods.include? :hello # => false

Khi method_missing được sử dụng method()s và respond_to?() cần được update luông vì vậy nó sẽ không trả về các câu trả lời không chính xác nữa.

Method_missing() có thể còn được sử dụng cho các công nghệ lập trình mạnh mẽ như tạo các đối tương mà thông điệp trả về với giá trị của các biến instance của nó.

class Person
  def initialize
    @first_name = 'bob'
    @last_name = 'lob'
  end
 
  def method_missing(name, *args)
    iv = "@#{name.to_s}"
    if instance_variables.include?(iv.to_sym)
      instance_variable_get(iv)
    else
      super
    end
  end
end
 
p = Person.new
# instance variables are accessible as methods
p.first_name # => "bob"
p.last_name # => "lob"
 
# Kernel#method_missing is still called for messages
# that don't correspond to instance variable names
p.phattie # => NoMethodError: undefined method `phattie'
 
# when new instance variables are defined, they are accessible
p.instance_variable_set(:@height, "six foot") # => "six foot"
p.height # => "six foot"

Method_missing() là một công nghệ lập trình ruby mạnh mẽ.

Link tham khảo

http://www.railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/ http://gshutler.com/2013/04/ruby-2-module-prepend/ http://rubyblog.pro/2016/08/module-prepend-in-ruby-2 https://codequizzes.wordpress.com/2014/04/24/rubys-method_missing/

0