12/08/2018, 09:27

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?

20150526_171909.jpg

"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 :

20150526_172541.jpg

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 :

  1. Nó sẽ tìm phương thức. Đây là một quá trình gọi tới method lookup
  2. 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

20150526_173311.jpg

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
0