12/08/2018, 16:22

Object và Class - Phần 2

Mỗi đối tượng tượng ghi nhớ class của nó bởi việc lưu một con trỏ đến cấu trúc RClass. Thông tin mà mỗi RClass chứa là gì? Chúng ta sẽ thấy gì nếu chúng ta có thể nhìn vào bên trong một class Ruby? Hãy xây dựng mô hình thông tin đại diện cho RClass. Mô hình này sẽ cho chúng ta một định nghĩa kỹ ...

Mỗi đối tượng tượng ghi nhớ class của nó bởi việc lưu một con trỏ đến cấu trúc RClass. Thông tin mà mỗi RClass chứa là gì? Chúng ta sẽ thấy gì nếu chúng ta có thể nhìn vào bên trong một class Ruby? Hãy xây dựng mô hình thông tin đại diện cho RClass. Mô hình này sẽ cho chúng ta một định nghĩa kỹ thuật class Ruby là gì, dựa trên những gì chúng ta biết các lớp class có thể làm. Mọi Ruby developer biết làm thế nào để viết một class: Bạn gõ từ khóa class, định nghĩa tên của một class mới và sau đó gõ vào các phương thức của class. Hãy nhìn ví dụ tương tự sau đây:

class Mathematician
  attr_accessor :first_name
  attr_accessor :last_name
end

attr_accessor là 1 cách viết tắt để định nghĩa phương thức get và set của một thuộc tính. Các phương thức attr_accessor cũng kiểm tra những giá trị nil. Ví dụ sau sẽ cho chúng ta thấy một cách lòng vòng để định nghĩa cùng một class Mathematician

class Mathematician
  def first_name
    @first_name
  end
  
  def first_name=(value)
    @first_name = value
  end
  
  def last_name
    @last_name
  end

  def last_name=(value)
    @last_name = value
  end
end

Có vẻ như lớp trên và mọi lớp Ruby chỉ là một nhóm các định nghĩa phương thức. Bạn có thể gán hành vi cho một đối tượng bằng cách thêm các phương thức vào class của nó và khi bạn gọi một phương thức trên một đối tượng, Ruby tìm kiếm phương thức trong class của đối tượng. Điều này dẫn đến định nghĩa đầu tiên của chúng ta về một class Ruby:

Một class Ruby là một nhóm các định nghĩa phương thức.

Vì vậy cấu trúc RClass cho Mathematician phải lưu một danh sách tất cả các phương thức được định nghĩa trong class giống như trong hình sau Chú ý ở đoạn code trên chúng ta đã tạo ra hai biến instance: @first_name và @last_name. Chúng ta đã thấy cách Ruby lưu các giá trị này vào mỗi cấu trúc RObject, nhưng bạn có thể nhận thấy rằng chỉ có các giá trị của các biến này được lưu trữ trong RObject chứ không phải tên của chúng (Ruby 1.8 lưu trữ các tên trong RObject). Ruby phải lưu trữ các thuộc tính trong RClass, điều này có ý nghĩa bởi vì các tên là giống nhau đối với mỗi instance Mathematician.

Hãy vẽ lại RClass lần nữa và bao gồm một bảng tên các thuộc tính, thể hiện trong hình dưới Bây giờ định nghĩa của chúng ta về một lớp Ruby như sau

Một lớp Ruby là một nhóm các định nghĩa phương thức và một bảng tên các thuộc tính

Ở đầu chương này, tôi đã đề cập rằng mọi giá trị trong Ruby đều là đối tượng. Điều này cũng đúng với các class. Hãy chứng minh điều này bằng cách sử dụng IRB.

> p Mathematician.class
=> Class

Như bạn có thể thấy, các lớp Ruby là tất cả các instance của lớp Class. Do đó, các lớp cũng là các đối tượng. Bây giờ hãy cập nhật định nghĩa của chúng ta về lớp Ruby một lần nữa

Một lớp Ruby là một đối tượng Ruby mà cũng chứa các định nghĩa phương thức và tên các thuộc tính

Bởi vì các lớp Ruby là những đối tượng, chúng ta biết rằng cấu trúc RClass cũng phải chứa một con trỏ lớp và một mảng các biến instance, những giá trị mà chúng ta biết đều được chứa trong đối tượng Ruby.

Như bạn thấy, tôi đã thêm một con trỏ vào lớp Class (là lớp của mọi đối tượng Ruby), tôi cũng đã thêm một bảng các biến instance. Đây là những biến instance thuộc cấp độ lớp. Đừng nhầm lẫn điều này với bảng tên các thuộc tính của biến instance cấp đối tượng. Điều này có vẻ làm chúng ta khó hiểu. Cấu trúc RClass có vẻ khó hiểu hơn cấu trúc RObject. Nhưng đừng lo chúng ta đã nhận được cấu trúc RClass. Tiếp theo, chúng ta cần xem xét hai thông tin quan trọng chứa trong lớp Ruby.

Kế thừa Kế thừa là một tính năng không thể thiếu của lập trình hướng đối tượng. Ruby thực hiện đơn kế thừa bằng cách tùy chỉnh chỉ định một supperclass khi chúng ta tạo ra một lớp. Nếu chúng ta không chỉ định một superclass, Ruby chỉ định lớp Object là supperclass. Ví dụ chúng ta có thể viết lại lớp Mathematician sử dụng superclass giống như sau:

class Mathematician < Person

Bây giờ mỗi instance của Mathematician sẽ bao gồm những phương thức tương tự mà Person có. Trong ví dụ này, chúng ta có thể muốn di chuyển các phương thức accessor first_name và last_name vào Person. Chúng ta cũng có thể di chuyển thuộc tính @first_name và @last_name vào lớp Person. Mọi instance của Mathematician đều chứa Mathematician những phương thức và thuộc tính này, mặc dù đã được di chuyển hết vào lớp Person. Lớp Mathematician phải có tham chiếu đến lớp Person (superclass của nó) để Ruby có thể tìm thấy bất kỳ phương thức hoặc thuộc tính được định nghĩa trong superclass. Chúng ta hãy cập nhật định nghĩa của chúng ta một lần nữa, giả sử rằng Ruby theo dõi các superclass bằng cách sử dụng con trỏ khác tương tự klass:

Một lớp Ruby là một đối tượng Ruby mà có chứa các định nghĩa phương thức, tên thuộc tính và một con trỏ superclass.

Chúng ta sẻ vẽ lại cấu trúc RClass bao gồm con trỏ superclass

Biến Instance Class và biến Class Một chút khó hiểu về cú pháp của Ruby là về các biến class. Bạn có thể nghĩ rằng đây chỉ đơn giản là các biến instance của một class, nhưng biến instance class và biến class khác biệt rõ rệt. Để tạo một biến instance class, đơn giản bạn chỉ tạo một biến instance sử dụng ký hiệu @, nhưng trong ngữ cảnh của một class chứ không phải một đối tượng. Ví dụ sau, chúng ta có thể sử dụng một biến instance của Mathematician để chỉ ra một nhánh của lớp Mathematician. Chúng ta tạo biến @type

class Mathematician
  @type = "General"
  def self.type
    @type
  end
end

puts Mathematician.type
=> General

Ngược lại, để tạo một biến class, bạn sử dụng ký hiệu @@, ví dụ biến @@type dưới đây

class Mathematician
  @@type = "General"
  def self.type
    @@type
  end
end

puts Mathematician.type
=> General

Có gì khác biệt? Khi bạn tạo ra một biến class, Ruby tạo ra một giá trị duy nhất cho bạn sử dụng trong lớp đo và trong bất kỳ lớp con nào bạn có thể định nghĩa. Mặt khác, sử dụng biến instance class, Ruby sẽ tạo ra giá trị riêng cho mỗi lớp hoặc lớp con. Hãy xem ví dụ sau, để xem cách Ruby xử lý hai loại biến khác nhau. Trước tiên chúng ta định nghĩa một biến class instance gọi là @type trong lớp Mathematician và gán giá trị string là General. Tiếp theo, chúng ta tạo ra 1 lớp thứ hai gọi là Statistician, là một lớp con của Mathematician và thay đổi giá trị của @type thành Statistics.

class Mathematician
  @type = "General"
  def self.type
    @type
  end
end

class Statistician < Mathematician
  @type = "Statistics"
end

puts Statistician.type
=> Statistics
puts Mathematician.type
=> General

Chú ý rằng, giá trị của @type trong Statistician và Mathematician là khác nhau. Mỗi lớp có một bản sao @type riêng. Tuy nhiên, nếu chúng ta sử dụng một biến class, Ruby sẽ chia sẻ giá trị giữa Mathematician và Statistician

class Mathematician
  @@type = "General"
  def self.type
    @@type
  end
end

class Statistician < Mathematician
  @@type = "Statistics"
end

puts Statistician.type
=> Statistics

puts Mathematician.type
=> Statistics

Ở đây, Ruby hiển thị cùng một giá trị @@type trong Statistician và Mathematician. Tuy nhiên, Ruby thực sự lưu cả biến class và instance class trong cùng một bảng bên trong cấu trúc RClass. Biểu tượng mở rộng @ trong tên cho phép Ruby phân biệt 2 loại biến.

Kết luận

Bài viết này chỉ là dịch lại phần Class trong chương 5: Object and Class của của cuốn sách Ruby Under a Microscope Mình đã dịch lại phần Object của chương này trong bài viblo trước đó. Rất mong nhận được những lời đóng góp ý kiến của mọi người.

0