Methods và block trong Ruby
This post hasn't been updated for 3 years
Method và block trong ruby
I. Method
Trước khi nói về methods tôi sẽ sơ lược một chút về object.
-
object là gì: nó là một instance của class, trong nó có chứa instance variables và instance methods
Ví dụ sau bạn sẽ thấy rõ:
class Student
def info
@name = "I'm Ruby"
end
end
2.1.5 :010 > st.methods.grep /info/
=> [:info]
2.1.5 :008 > st.instance_variables
=> [:@name]
Theo ví dụ ở trên thì st
là một object của class Student
.
1. Instance method
- Là method của instance object, có thể gọi nó thông qua object. Như ví dụ trên ta có thể nói
info
là một method củast
(hay chính xác hơn nó là một instance method của classStudent
).
2.1.5 :012 >Student.instance_methods.grep /info/
=> [:info]
2.1.5 :013 > st.info
=> "I'm Ruby"
Nhưng không được nói info
là một method của Student
như vậy sẽ bị nhầm sang class method.
2.1.5 :015 > st.methods == Student.instance_methods
=> true
2.1.5 :016 > Student.methods.grep /info/
=> []
object's method không "sống" trong object mà nó "sống" trong clas của object, chính vì thế mà nó được gọi là instance method của class.
2. Method Lookup
Trước khi đi vào method look ta cần hiểu về hai khái niệm receiver
và ancestors chain
receiver
là gì? Đơn giản nó chỉ là một object và gọi được phương thức từ nó. Như trên thìst
là mộtreceiver
ancestors chain
: trước khi đi vào định nghĩa tôi có một ví dụ sau:
class Animal
def weight
puts "10kg"
end
end
class Cat < Animal
end
2.1.5 :025 > ca = Cat.new
=> #<Cat:0x000000022bec18>
2.1.5 :026 > ca.weight
10kg
Khi gọi ca.weight, ruby sẽ tìm trong class ca instance method weight
nhưng không thấy, tiếp tục tìm đến superclass tức là class mà nó kế thừa (ở đây là Animal), sau khi tìm thấy nó sẽ dừng lại và thực thi method. Nhưng giả sử nó không tìm thấy ở class nó kế thừa Animal nó sẽ tiếp tục tìm lên superclass của class Animal là Object. Path từ object -> class -> superclass -> superclass... như vậy được gọi là ancestors chain
2.1.5 :033 > ca.class.ancestors
=> [Cat, Animal, Object, Kernel, BasicObject]
Vậy có thể kết luận như sau: Method lookup là việc tìm kiếm phương thức, Ruby sẽ tìm trong class của receiver
và từ đó nó sẽ tìm method theo ancesstors chain
cho đến khi nó tìm được phương thức cần gọi.
Ngoài ra ancesstors chain
còn bao gồm cả module
module M
def name
"Reck"
end
end
class Dog
include M
end
2.1.5 :034 > dg = Dog.new
=> #<Dog:0x0000000144a1f0>
2.1.5 :035 > dg.name
=> "Reck"
2.1.5 :036 > dg.class.ancestors
=> [Dog, M, Object, Kernel, BasicObject]
3. Thực thi Method
Sau khi đã tìm được phương thức theo ancesstor chain
Ruby sẽ thực thi phương thức mà nó tìm được.
Khi gọi một method thì receiver
sẽ trở thành self
(curent object) tất cả các instance variables của receiver
sẽ là instance của self, và tất cả các phương thức gọi không tường minh bên trong nó tương đương với việc gọi self.my_instance_method
.
class MyClass
def testing_self arg
@var = 10 # 1 biến instance của self
my_instance_method(arg) # giống như gọi self.my_instance_method(arg)
end
def my_instance_method arg
@var = @var + arg
end
end
4. Dynamic Method
a. Gọi phương thức động
Thông thường khi gọi một phương thức bạn thường sử dụng object + dấu chấm + phương thức , ngoài ra thì có thể gọi phương thức thông qua cách khác đó là sử dụng phương thức send
của Object
Theo ví dụ MyClass trên ta có thể gọi được instance method của nó thông qua send
2.1.5 :010 > my = MyClass.new
=> #<MyClass:0x00000001cc2cd8>
2.1.5 :011 > my.testing_self 1
=> 11
2.1.5 :012 > my.send(:my_instance_method, 10)
Tham số đầu tiên trong phương thức send là tên phương thức, tham số sau là tham số của phương thức. Phương thức send này rất hữu ích trong việc tránh trùng lặp code, làm cho code của bạn DRY hơn, để thấy được điều đó bạn xem ví dụ tiếp theo:
class Animal
def action arg
if arg == "run"
self.run
elsif arg = "eat"
self.eat
end
end
def run
"Runing..."
end
def eat
"Eating..."
end
end
Và giờ bạn refactoring nó với phương thức send
class Animal
def action arg
self.send(arg)
end
def run
"Runing..."
end
def eat
"Eating..."
end
end
Bạn thấy đó send đã giải quyết vấn đề if else làm cho code của bạn DRY hơn.
Chú ý thêm là send có thể nhận tên phương thức truyền vào theo hai kiểu string và symbol.
b. Định nghĩa một phương thức động
Bạn định nghĩa dynamic method phương thức define_method()
class Cat
define_method :fingers do |arg|
arg * 4
end
end
2.1.5 :018 > ca = Cat.new
=> #<Cat:0x00000001aa9370>
2.1.5 :019 > ca.fingers 1
=> 4
define_mehtod()
được thực thi bên trong class Cat và nó được định nghĩa như một instance methods của class Cat, công nghệ định nghĩa phương thức tại thời điểm runtime như vậy được gọi là Dynamic Method.
class Animal
def self.define_action name
define_method(name) do
"#{name.capitalize}ing..."
end
end
define_action :run
define_action :eat
define_action :drink
end
2.1.5 :021 > a = Animal.new
=> #<Animal:0x00000002caf470>
2.1.5 :022 > a.run
=> "Runing..."
2.1.5 :023 > a.eat
=> "Eating..."
define_method()
được thực thi bên trong class Animal do đó để định nghĩa dynamic method trong một phức thức khác thì ta phải định nghĩa nó trong một class method.
Việc định nghĩa dynamic method một cách hợp lý sẽ giúp code của bạn ngắn gọn hơn rất nhiều.
**5. Method missing **
Khi bạn gọi một phương thức, ruby sẽ tìm phương thức dựa trên ancesstor chain, nếu không tìm thấy phương thức nó sẽ gọi đến phương thức method_missing()
và rais ra message NoMethodError,
2.1.5 :025 > a.walk
NoMethodError: undefined method `walk' for #< Animal:0x00000002caf470>
Bạn có thể override lại phương thức method_missing
để custom tùy theo ý mình
class Animal
def method_missing(method, *args)
puts "Method #{method} that you've called does not exist"
end
end
2.1.5 :040 > a = Animal.new
=> #<Animal:0x00000002b0c438>
2.1.5 :041 > a.lfkajds
Method lfkajds that you've called does not exist
Giờ ta sẽ refactoring lại class Animal mà sử dụng define_method phía trên
class Animal
def method_missing(method_name, *args)
"#{method_name.capitalize}ing..."
end
end
2.1.5 :047 > a = Animal.new
=> #<Animal:0x00000002cb2f08>
2.1.5 :048 > a.eat
=> "Eating..."
Giờ bạn có thể gọi các phương thức drink, eat, run như bạn đã làm đối với define_method. Lúc này drink, eat, run ... được gọi là Ghost Method bởi nó chỉ được gọi ở phía người gọi mà bạn không tìm thấy nó được ở receiver.
Hãy so sánh số dòng code của class Animal ở hai cả 3 cách viết send, define_method và method_missing rõ ràng là method_missing đã ngắn hơn rất nhiều.
II. Block
Block là một phần của "callable object(bao gồm procs, lambda ...)", nó được định nghĩa với ngoặc xoắn hoặc keyword do .... end
, bạn chỉ có thể định nghĩa một block khi bạn gọi phương thức và phương thức có thể gọi ngược trở lại block thông qua từ khóa yeild
.
class Animal
def super_animal arg1, arg2
yield(arg1, arg2)
end
end
2.1.5 :013 > a.super_animal(2, 2){|x, y| x*x + y*y}
=> 8
Nếu sử dụng yield trong method mà lúc gọi ta không định nghĩa block cùng method sẽ sinh ra lỗi.
2.1.5 :014 > a.super_animal(2, 2)
LocalJumpError: no block given (yield)
def super_animal arg1, arg2
yield(arg1, arg2) if block_given?
return "plz define your block"
end
2.1.5 :024 > a.super_animal(2,3)
=> "plz define your block"
Tips:
2.1.5 :045 >arr = [1,2,3,4]
=> [1, 2, 3, 4]
2.1.5 :046 > arr.map(&:to_s)
=> ["1", "2", "3", "4"]
Việc gọi map bên trên tương đương với:
2.1.5 :009 > arr.map do |ar|
2.1.5 :010 > ar.to_s
2.1.5 :011?> end
=> ["1", "2", "3", "4"]
Proc
- Proc là một block, bạn có thể định nghĩa một proc với từ khóa
Proc.new
và kết hợp với block theo sau nó, nó là mộtanonymous functions
- Proc là block rồi tại sao lại cần đến nó là vì khi nào bạn muốn tái sử dụng block thì định nghĩa một proc và call nó khi nào cần thiết ngoài ra thì bạn cũng có thể sử dụng proc một cách trực tiếp ngay khi định nghĩa proc
2.1.5 :012 > pr = Proc.new{|name| "Hello #{name}" }
=> #<Proc:0x00000000f0f0a0@(irb):12>
2.1.5 :013 > pr.call("Ruby")
=> "Hello Ruby"
Lambda
- Lambda cũng giống như một proc
2.1.5 :014 > la = lambda{|x| "I'm a #{x}"}
=> #<Proc:0x00000000e93b30@(irb):14 (lambda)>
2.1.5 :015 > la.call("Lambda")
=> "I'm a Lambda"
Có 2 điểm khác biệt giữa proc và lambda
- Về tham số truyền vào: proc sẽ không check số tham số truyền vào ngược lại lambda thì có check:
2.1.5 :016 > la.call("Lambda", "San")
ArgumentError: wrong number of arguments (2 for 1)
from (irb):14:in `block in irb_binding'
from (irb):16:in `call'
from (irb):16
from /home/khanh/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
Trong khi đó không có lỗi sinh ra với proc
2.1.5 :017 > pr.call("aa", "bb")
=> "Hello aa"
Về return và giá trị trả về: Khi return trong proc thì mehod mà gọi proc sẽ dừng và trả về giá trị nhưng đối với lambda thì không khi gặp phải return nó sẽ trả về giá trị và method chứa nó vẫn tiếp tục chạy. Ví dụ dưới đây sẽ cho bạn thấy rõ điều đó
2.1.5 :001 > class Animal
2.1.5 :002?> def pr_ex
2.1.5 :003?> Proc.new{return "Proc val"}.call
2.1.5 :004?> puts "return value pr_ex"
2.1.5 :005?> end
2.1.5 :006?> def ld_ex
2.1.5 :007?> lambda { return "lambda" }.call
2.1.5 :008?> puts "return value ld_ex"
2.1.5 :009?> end
2.1.5 :010?> end
=> :ld_ex
a = Animal.new
2.1.5 :014 > a.pr_ex
=> "Proc val"
2.1.5 :015 > a.ld_ex
return value ld_ex
=> nil
Như bạn thấy rõ ràng từ hai method trên thì đối với proc nó sẽ không chạy lệnh puts "return value pr_ex"
nhưng lambda thì lại khác nó tiếp tục chạy dòng lệnh puts "return value ld_ex"
All Rights Reserved