Class definitions

Ruby là một ngôn ngữ rất thú vị, và mình khá chắc chắn rằng bạn sẽ còn nhiều điều chưa biết về nó. Hôm nay mình sẽ tiếp tục đề cập tới class difinitions.

material.png

Inside Class Definitions

Bạn có bao giờ nghĩ rằng bạn có thể định nghĩa một lớp ở nơi mà bạn định nghĩa một phương thức? Thực tế thì bạn có thể viết bất cứ đoạn code nào mà bạn muốn trong định nghĩa class.

class MyClass
	puts 'Hello!'
end

=> Hello!

Khi bạn định nghĩa một lớp thì nó cũng trả về giá trị của câu lệnh cuối cùng, nó giống như phương thức hay block đã làm.

result = class MyClass
	self
end

 => MyClass

Ví dụ này nhấn mạnh rằng trong định nghĩa lớp(hoặc module), thì self ở đây cũng chính là bản thân class đó. Như vậy “những class và module cũng chỉ là những đối tượng ?”

The Current Class

Như bạn đã biết, ở trong chương trình Ruby, bạn luôn có một đối tượng hiện tại đó là self. Và cũng như thế thì bạn cũng luôn có một class hiện tại (hoặc module hiện tại). Khi bạn định nghĩa một phương thức, phương thức đó trở thành phương thức thể hiện của class hiện tại.

Mặc dù bạn có thể lấy ra một tham chiếu tới đối tượng hiện tại thông qua self, nhưng ở đây không có từ khóa tương đương để lấy ra một tham chiếu tới lớp hiện tại (hoặc module hiện tại). Tuy nhiên, nó cũng không khó để có thể theo dõi và chỉ ra lớp hiện tại bằng cách nhìn vào các đoạn mã. Bất cứ khi nào bạn mở một class với từ khóa là class (hoặc một module với từ khóa là module), thì lúc đó class sẽ trở thành class hiện tại.

class MyClass
	# The current class is now MyClass...
    def my_method
    	# ...so this is an instance method of MyClass
    end
end

Tuy nhiên, từ khóa class cũng có một giới hạn, đó là nó cần tên của một class. Thật không may là trong một vài trường hợp bạn không biết tên của class mà bạn muốn mở ra. Ví dụ như bạn có một phương thức, và trong phương thức đó sẽ thêm một phương thức thể hiện cho một class khác.

def add_method_to(a_class)
	# TODO: define method m() on a_class
end

Làm thế nào để bạn có thể làm được nếu bạn không biết tên của nó? Bạn cần một vài cách khác, không phải từ khóa class để thay cho class hiện tại. Bạn có thể dùng phương thức class_eval()


class OtherClass
end

class MyClass
    def add_method_to(a_class)
        a_class.class_eval do
        def say_hello; 'Hello!' ; end
        end
    end
end

2.1.5 :015 > object1 = MyClass.new
 => #<MyClass:0x000000010e2eb8>

2.1.5 :016 > object2 = OtherClass.new
 => #<OtherClass:0x000000010e74b8>

2.1.5 :017 > object2.say_hello()
NoMethodError: undefined method `say_hello' for #<OtherClass:0x000000010e74b8>

2.1.5 :018 > object1.add_method_to(OtherClass)
 => :say_hello

2.1.5 :019 > object2.say_hello()
 => "Hello!"

Sau ví dụ trên bạn sẽ thắc mắc sự khác nhau giữa class_eval() và instance_eval(), cái mà bạn đã được biết trước đó. Phương thức instance_eval() chỉ thay đổi self, trong khi đó class_eval() sẽ thay đổi cả self và class hiện tại.

Vậy lúc nào thì dùng instance_eval(), lúc nào thì dùng class_eval() ? Nếu bạn muốn mở một đối tượng, không phải là một class thì bạn có thể dùng instance_eval(). Nếu bạn mở một lớp và định nghĩa một phương thức với def thì nên chọn class_eval() Nếu tất cả những gì bạn muốn chỉ là thay đổi self thì bạn đều có thể dùng instance_eval() hoặc class_eval(). Tuy nhiên, bạn nên chọn cách tốt nhất cho yêu cầu của bạn. Nếu bạn đang muốn mở một đối tượng và không cần quan tâm tới class của nó thì bạn dùng instance_eval() . Còn nếu bạn nghĩ rằng bạn muốn một Open Class ở đây, thì chắc chắn là class_eval() sẽ tốt hơn.

Module # class_eval () thực sự linh hoạt hơn so với class. Bạn có thể sử dụng class_eval () cho bất kỳ biến tham chiếu đến lớp, trong khi class đòi hỏi một hằng số. Ngoài ra, class sẽ mở ra một phạm vi mới, thay đổi các ràng buộc hiện tại, trong khi class_eval () có một Flat Scope.

Class Instance Variables

Trong một class thì vai trò của self thuộc về class, vì thế các biến thực thể như @my_var ở ví dụ dưới đây thuộc về class. Đừng bị nhầm lẫn, các biến thực thể của class khác với các biến thực thể của các đối tượng của lớp đó.


class MyClass
@my_var = 1

def self.read; @my_var; end
def write; @my_var = 2; end
def read; @my_var; end

end

Ở ví dụ trên định nghĩa 2 biến thực thể, và cả 2 biến đều cùng tên là @my_var, tuy nhiên 2 biến này có phạm vi khác nhau, và chúng ccũng thuộc về các đối tượng khác nhau. Để hiểu rõ các làm việc của chúng, bạn cần nhớ rằng các lớp cũng chỉ là các đối tượng, và bạn có thể theo dõi self thông qua chương trình. Một biến @my_var được định nghĩa với obj là self, vì thế nó là một biến thực thể của đối tượng obj. Biến @my_var khác được định nghĩa với MyClass là self,, vì thế nó là một biến thực thể của đối tượng MyClass.

Singleton Methods

Trong Ruby một phương thức cụ thể riêng cho một đối tượng, phương thức đó được gọi là singleton method. Các bạn xem ví dụ dưới đây để hiểu rõ hơn về singleton method.

irb(main):003:0> time_now = Time.now
=> 2015-08-20 10:55:04 +0700
irb(main):004:0> def time_now.common_format
irb(main):005:1> self.strftime("%m/%d/%Y")
irb(main):006:1> end
=> :common_format

irb(main):007:0> time_now.common_format
=> "08/20/2015"
irb(main):009:0> time_now.methods.grep(/common_format/)
=> [:common_format]
irb(main):010:0> time_now.singleton_methods
=> [:common_format]

irb(main):011:0> new_time_now = Time.now
=> 2015-08-20 10:59:25 +0700
irb(main):012:0> new_time_now.common_format
NoMethodError: undefined method `common_format' for 2015-08-20 10:59:25 +0700:Time

Như ví dụ trên thì mình đã thêm một singleton method cho đối tượng time_now, đây là đối tượng của class Time. Việc thêm singleton method như thế chỉ áp dụng riêng cho đối tượng mà chúng ta thêm vào chứ không áp dụng cho tất cả đối tượng của class. Ngay sau đó mình đã tạo ra một đối tượng là new_time_now và gọi singleton method trước đó nhưng không thể sử dụng được.

** The Truth About Class Methods**

Nêú bạn để ý thì có thể thấy rằng cách khai báo của class method và singleton method cho đối tượng cũng có “sự giống nhau”. Sau đây là 3 cách thêm một method cho một class mà chúng ta thường dùng.

class MyClass
    def self.method_1
    	#TODO something
    end

    def MyClass.method_2
    	#TODO something
    end

    class << self
        def method_3
        	#TODO something
        end
    end
end

Các bạn có thể để ý rằng cách định nghĩa và cách gọi của method_1 và method_2 chính là cách mà chúng ta dùng cho singleton method. Bởi vì một class cũng chính là một đối tượng nên chúng ta có thể suy luận rằng “một method của class cũng chính là singleton method của class đó”

irb(main):029:0> MyClass.singleton_methods

=> [:method_1, :method_2, :method_3]

Eigenclasses

Như mình đã viết ở blog trước về cách mà Ruby tìm một phương thức cho đối tượng, cho class.class. Nó sẽ đi vào phía bên phải của class receiver và sau đó đi lên theo accesstor_chain.

class MyClass
def my_method; end
end
obj = MyClass.new
obj.my_method

Khi bạn gọi phương thức my_method, nó sẽ đi sang bên phải MyClass và tìm kiếm phương thức ở đây. Vậy điều gì sẽ xảy ra nếu bạn định nghĩa một singleton method cho đối tượng.

def obj.my_singleton_method; end

Singleton method không thể tồn tại bên trong obj, bởi vì nó không phải là một class. Nó cũng không thể tồn tại trong MyClass bởi vì tất cả các đối tượng khác của MyClass không thể sử dụng. Và nó lại càng không phải là một phương thức thể hiện trong lớp cha của MyClass, Object. Vậy cuối cùng thì singleton method tồn tại ở đâu?

Nó tồn tại trong một class ẩn, một class rất đặc biệt gọi là eigen-class của object.

obj = Object.new
eigenclass = class << obj
    self
end

irb(main):034:0> eigenclass.class
=> Class

Khi gọi một phương thức từ một object, đâu tiên Ruby sẽ tìm kiếm phương thức bên trong eigen-class trước, nếu ruby không tìm thấy phương thức với eigen-class thì nó mới tìm theo ancestors chain.

irb(main):035:0> def obj.my_singleton_method; end
=> :my_singleton_method
irb(main):036:0> eigenclass.instance_methods.grep(/my_/)
=> [:my_singleton_method]

Aliases

Trong Ruby cho phép bạn đặt các alias name cho các phương thức.

class People
    def hello
    	puts  "hello everybody"
    end
    alias :h :hello
end

irb(main):009:0> nick = People.new
=> #<People:0x00000003dbcf38>
irb(main):010:0> nick.h
hello everybody

Như vậy chúng ta đã tìm hiểu thêm được về class definitions, mong gặp các bạn ở các blog tiếp theo.


All Rights Reserved