Object trong Ruby
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 ...
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ết my_string.reverse() thì my_string chính là receiver.
- ancestors chain thì sao, để hiểu về khái niệm của một ancestors 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