Object trong Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Bạn là một lập trình viên Ruby on Rails và bạn làm việc hằng ngày, hằng giờ với các đối tượng, nhưng liệu bạn có chắc bạn hiểu hết về nó? Sau đây mình sẽ chỉ ra một số thành phần của đối tượng trong Ruby để chúng ta có thể hiểu rõ hơn về nó.
Instance Variables
Các đối tượng luôn chứa các instance variables, là các biến thực thể. Bạn có thể liệt kê danh sách các biến thực thể bằng cách gọi Object#instance_variables(). Ví dụ :
class Chicken
def set_weight x
@weight = x
end
end
irb(main):110:0> obj1 = Chicken.new
=> #<Chicken:0x000000032ddd88>
irb(main):111:0> obj1.set_weight 123
=> 123
irb(main):112:0> obj1.instance_variables
=> [:@weight]
Không giống như trong Java hoặc các ngôn ngữ khác, trong Ruby không có kết nối giữa một lớp đối tượng và các biến thực thể. Biến thực thể chỉ thực sự hiện hữu khi bạn gán cho chúng 1 giá trị, cũng vì thế bạn có thể có những đối tượng của cùng một lớp nhưng lại có những cài đặt khác nhau cho các biến thực thể. Ví dụ :
irb(main):113:0> obj2 = Chicken.new
=> #<Chicken:0x000000032a86d8>
irb(main):114:0> obj3 = Chicken.new
=> #<Chicken:0x00000002d67020>
irb(main):115:0> obj3.set_weight 100
=> 100
irb(main):116:0> obj2.instance_variables
=> []
irb(main):117:0> obj3.instance_variables
=> [:@weight]
Method
Bên cạnh có biến thực thể, đối tượng cũng có các phương thức. Bạn có thể liệt kê danh sách các phương thức của đối tượng bằng cách gọi Object#methods()
. Hầu hết các đối tượng kế thừa một số các phương thức từ Object, vì vậy danh sách các phương thức này đều khá dài. Bạn có thể sử dụng Array#grep()
để hiển thị cho bạn đúng phương thức mà bạn muốn thấy.
irb(main):118:0> obj3.methods
=> [...quá nhiều...]
irb(main):119:0> obj3.methods.grep(/weight/)
=> [:set_weight]
Tuy nhiên bạn có để ý rằng ở trong đối tượng không thực sự mang một danh sách các phương thức. Ở bên trong, một đói tượng chỉ đơn giản là chứa các biến thực thể của nó và một tham chiếu đến lớp của nó. Vì vậy đâu là methods?
"Các đối tượng đều chung một class cũng như cùng các phương thức, vì vậy các phương thức cần phải chưa bên trong class chứ không thể trong object"
Bạn có thể nói một cách chính xác là "obj có một phương thức tên là set_weight()
" nghĩa là bạn có thể gọi obj.set_weight()
. Ngược lại, bạn không thể nói là "class Chicken có một phương thức tên là set_weight()
." Nó sẽ làm rối tung lên, bởi vì nó sẽ ngụ ý là bạn có thể gọi Chicken.set_weight()
.
Để bỏ đi sự nhập nhằng này, bạn nên nói là set_weight() là một phương thức thực thể (chứ không phải chỉ là một phương thức) của class Chicken, nghĩa là nó định nghĩa trong Chicken, và bạn thực sự cần một thực thể (một thể hiện) của Chicken để gọi nó. Nó là cùng một phương thức, nhưng khi bạn nói về một lớp, bạn gọi nó là một phương thức thực thể, và khi bạn nói về đối tượng, bạn gọi nó một cách đơn giản là một phương thức. Hãy nhớ điểm đặc biệt này, và bạn không sẽ không bị bối rối khi viết các đoạn mã như thế này :
irb(main):124:0> String.instance_methods == "text".methods
=> true
irb(main):125:0> String.methods == "text".methods
=> false
Class
Trong Ruby một đối tượng sẽ thuộc một class nào đó, nhưng chính class cũng là đối tượng. Và vì class cũng là một đối tượng nên mọi thứ áp dụng cho đối tượng đều có thể áp dụng cho các class.
irb(main):126:0> 123.class
=> Fixnum
irb(main):127:0> Fixnum.class
=> Class
irb(main):128:0> "123".class
=> String
irb(main):129:0> String.class
=> Class
Giống như mọi đối tượng, các class cũng có những phương thức. Các phương thức của các đối tượng cũng đồng thời là phương thức thực thể cho class của nó.
irb(main):133:0> inherited = false
=> false
irb(main):134:0> Class.instance_methods(inherited)
=> [:allocate, :new, :superclass]
Và cũng như các đối tượng thì class cũng sẽ có supper class của nó
irb(main):135:0> String.superclass
=> Object
irb(main):136:0> Object.superclass
=> BasicObject
irb(main):137:0> BasicObject.superclass
=> nil
Vậy tất cả class cuối cùng đều kế thừa từ Object, và nó được kế thừa từ BasicObject, gốc của hệ thống lớp trong Ruby.
Kể cả Class
và Moudle
cũng vậy.
irb(main):138:0> Class.superclass
=> Module
irb(main):139:0> Module.superclass
=> Object
Vậy, một class chỉ là một module cải biến với việc bổ sung 3 phương thức, new(), allocate(). và superclass() nó cho phép bạn có thể tạo ra các đối tượng hoặc sắp xếp các lớp bên trong hệ thống. Hầu hết những gì bạn sẽ học về các class cũng sẽ áp dụng cho các module, và ngược lại. Và để có thể hình dung được rõ hơn thì chúng ta có thể tóm gọn object, class, module với hình sau đây :
Gọi một phương thức từ object
Có bao giờ bạn tự hỏi rằng điều gì sẽ xảy ra khi bạn gọi một phương thức từ object trong ruby? Khi bạn gọi một phương thức, ruby sẽ làm 2 việc :
- Nó sẽ tìm phương thức. Đây là một quá trình gọi tới
method lookup
- Chạy phương thức, Để làm việc này Ruby cần một vài thứ để gọi
self
.
Bước 1 : quá trình tìm phương thức (method lookup
)
Để hiểu rõ quá trình này bạn cần biết về hai khái niệm là ancestors chain
và ancestors chain
Receiver
chỉ đơn giản là đối tượng mà bạn gọi một phương thức từ đó. Ví dụ, nếu bạn viếtmy_string.reverse()
thìmy_string
chính làreceiver
.ancestors chain
thì sao, để hiểu về khái niệm của mộtancestors chain
, chỉ cần nhìn vào mọi lớp Ruby. Sau đó tưởng tượng di chuyển từ lớp vào trong lớp cha của nó, sau đó tới lớp cha của lớp cha, và cứ như thế cho tới khi bạn tìm tới Object(là lớp cha mặc định) và sau đó cuối cùng là BasicOject (gốc của các lớp kế thừa). Đường dẫn của các lớp bạn đi qua đó chính làancestors chain
của một class.
Bây giờ bạn đã biết cái gì là ancestors chain
và cái gì là một ancestors chain
, bạn có thể tóm tắt quá trình của phương thức lookup trong một câu đơn : để tìm một phương thức, Ruby đi tới lớp của ancestors chain
và từ đó nó đi theo ancestors chain
cho tới khi tìm tới phương thức. ví dụ.
class Animal
def run
puts "Running..."
end
end
class Pig < Animal
end
irb(main):009:0> obj = Pig.new
=> #<Pig:0x0000000204ddd0>
irb(main):010:0> obj.run # => "run"
Running...
=> nil
Nhìn vào hình trên bạn có thể nhìn thấy một quy tắc gọ là "một bước sang bên phải, sau đó đi lên".
Là quy tắc một bước sang bên phải vào trong lớp của receiver
, và sau đó đi lên theo ancestors chain
cho tới khi bạn tìm thấy phương thức.
Để chỉ ra ancestors chain
của một class thì Ruby cũng cung cấp một phương thức là ancestors.
irb(main):012:0> Pig.ancestors
=> [Pig, Animal, Object, Kernel, BasicObject]
Chú ý là ancestors chain
cũng bao gồm cả các modules.
module M
def module_method
'M#module_method()'
end
end
class C
include M
end
class D < C; end
irb(main):024:0> D.new.module_method
=> "M#module_method()"
irb(main):025:0> D.ancestors
=> [D, C, M, Object, Kernel, BasicObject]
Bước 2 : thực thi method
Khi bạn đã tìm được phương thức với method lookup
, Ruby sẽ thực hiện phương thức.
Đối tượng hiện tại, là đối tượng gọi phương thức sẽ trở thành self, tất cả các instance variables
của nó sẽ là instance
của self
.
class MyClass
def testing_self
@var = 10
# An instance variable of self
my_method()
# Same as self.my_method()
self
end
def my_method
@var = @var + 1
end
end
irb(main):024:0> obj = MyClass.new
=> #<MyClass:0x00000001d52860>
irb(main):025:0> obj.testing_self
=> #<MyClass:0x00000001d52860 @var=11>
Nhưng điều gì sẽ xẩy ra khi không tìm ra phương thức? Lúc đó sẽ thực thi phương thức kiểu gì?
Khi bạn gọi một phương thức từ một đối tượng và không tìm ra phương thức đó, lúc đó Ruby sẽ gọi tới phương thức method_missing()
và chỉ ra cho bạn lỗi mà bạn đang gặp phải.
irb(main):026:0> obj.dance_dance_dance
NoMethodError: undefined method `dance_dance_dance' for #<MyClass:0x00000001d52860 @var=11>
Trong một vài trường hợp bạn sẽ cần phải custom lại phương thức method_missing()
cho phù hợp với nhu cầu.
class Human
def method_missing(method, *args)
puts "Không tồn tại phương thức #{method} trong class Human!"
end
end
irb(main):037:0> obj_human = Human.new
=> #<Human:0x00000001cda568>
irb(main):038:0> obj_human.dance_dance_dance
Không tồn tại phương thức dance_dance_dance trong class Human!
=> nil
All rights reserved