Computer >> Máy Tính >  >> Lập trình >> Ruby

Làm sáng tỏ các lớp, cá thể và kính đo trong Ruby

Chào mừng đến với một tập mới của Ruby Magic! Ấn bản của tháng này là về kính thiên văn, một chủ đề được khơi mào bởi cuộc thảo luận giữa hai nhà phát triển (Xin chào Maud!).

Thông qua việc kiểm tra metaclasses, chúng ta sẽ tìm hiểu cách thức hoạt động của các phương thức class và instance trong Ruby. Trong quá trình này, hãy khám phá sự khác biệt giữa việc xác định một phương thức bằng cách chuyển một "definee" rõ ràng và sử dụng class << self hoặc instance_eval . Đi thôi!

Phiên bản lớp và Phương thức phiên bản

Để hiểu lý do tại sao metaclasses được sử dụng trong Ruby, chúng ta sẽ bắt đầu bằng cách kiểm tra sự khác biệt giữa các phương thức instance- và class.

Trong Ruby, một lớp là một đối tượng xác định một bản thiết kế để tạo ra các đối tượng khác. Các lớp xác định những phương thức nào có sẵn trên bất kỳ phiên bản nào của lớp đó.

Việc xác định một phương thức bên trong một lớp sẽ tạo ra một phương thức thể hiện trên lớp đó. Bất kỳ phiên bản nào trong tương lai của lớp đó sẽ có sẵn phương thức đó.

class User
  def initialize(name)
    @name = name
  end
 
  def name
    @name
  end
end
 
user = User.new('Thijs')
user.name # => "Thijs"

Trong ví dụ này, chúng tôi tạo một lớp có tên là User , với một phương pháp phiên bản được đặt tên là #name trả về tên của người dùng. Sử dụng lớp, sau đó chúng tôi tạo một cá thể lớp và lưu trữ nó trong một biến có tên user . Kể từ khi user là một bản sao của User lớp, nó có #name phương pháp có sẵn.

Một lớp lưu trữ các phương thức thể hiện của nó trong bảng phương thức . Bất kỳ phiên bản nào của lớp đó đều tham chiếu đến bảng phương thức của lớp đó để có quyền truy cập vào các phương thức cá thể của nó.

Đối tượng lớp

A phương thức lớp là một phương thức có thể được gọi trực tiếp trên lớp mà không cần phải tạo một thể hiện trước. Phương thức lớp được tạo bằng cách thêm tiền tố vào tên của nó bằng self. khi định nghĩa nó.

Bản thân một lớp là một đối tượng. Một hằng số tham chiếu đến đối tượng lớp, vì vậy các phương thức lớp được định nghĩa trên nó có thể được gọi từ bất kỳ đâu trong ứng dụng.

class User
  # ...
 
  def self.all
    [new("Thijs"), new("Robert"), new("Tom")]
  end
end
 
User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]

Các phương thức được xác định bằng một self. -prefix không được thêm vào bảng phương thức của lớp. Thay vào đó, chúng được thêm vào siêu kính của lớp.

Kính đồng hồ

Ngoài một lớp, mỗi đối tượng trong Ruby có một siêu kính ẩn. Metaclasses là những chiếc kính đơn, có nghĩa là chúng thuộc về một đối tượng duy nhất. Nếu bạn tạo nhiều bản sao của một lớp, chúng sẽ chia sẻ cùng một lớp, nhưng tất cả chúng sẽ có kính đo riêng biệt.

thijs, robert, tom = User.all
 
thijs.class # => User
robert.class # => User
tom.class # => User
 
thijs.singleton_class  # => #<Class:#<User:0x00007fb71a9a2cb0>>
robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>>
tom.singleton_class    # => #<Class:#<User:0x00007fb71a9a2c10>>

Trong ví dụ này, chúng ta thấy rằng mặc dù mỗi đối tượng có lớp User , các lớp singleton của chúng có các ID đối tượng khác nhau, nghĩa là chúng là các đối tượng riêng biệt.

Bằng cách có quyền truy cập vào một siêu kính, Ruby cho phép thêm các phương thức trực tiếp vào các đối tượng hiện có. Làm như vậy sẽ không thêm một phương thức mới vào lớp của đối tượng.

robert = User.new("Robert")
 
def robert.last_name
  "Beekman"
end
 
robert.last_name # => "Beekman"
User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>)

Trong ví dụ này, chúng tôi thêm một #last_name cho người dùng được lưu trữ trong robert Biến đổi. Mặc dù robert là một bản sao của User , mọi phiên bản mới được tạo của User sẽ không có quyền truy cập vào #last_name , vì nó chỉ tồn tại trên robert Của siêu kính.

self là gì ?

Khi xác định một phương thức và chuyển một bộ thu, phương thức mới sẽ được thêm vào siêu kính của bộ thu, thay vì thêm nó vào bảng phương thức của lớp.

tom = User.new("Tom")
 
def tom.last_name
  "de Bruijn"
end

Trong ví dụ trên, chúng tôi đã thêm #last_name trực tiếp trên tom đối tượng, bằng cách chuyển tom với tư cách là người nhận khi xác định phương thức.

Đây cũng là cách nó hoạt động cho các phương thức lớp.

class User
  # ...
 
  def self.all
    [new("Thijs"), new("Robert"), new("Tom")]
  end
end

Ở đây, chúng tôi chuyển self một cách rõ ràng với tư cách là người nhận khi tạo .all phương pháp. Trong định nghĩa lớp, self đề cập đến lớp (User trong trường hợp này), vì vậy .all phương thức được thêm vào User của siêu kính.

Bởi vì User là một đối tượng được lưu trữ trong một hằng số, chúng tôi sẽ truy cập vào cùng một đối tượng — và cùng một siêu kính — bất cứ khi nào chúng tôi tham chiếu đến nó.

Mở Metaclass

Chúng tôi đã biết rằng các phương thức lớp là các phương thức trong siêu kính của đối tượng lớp. Biết được điều này, chúng ta sẽ xem xét một số kỹ thuật tạo phương thức lớp khác mà bạn có thể đã thấy trước đây.

class << self

Mặc dù nó đã lỗi thời một chút, một số thư viện sử dụng class << self để định nghĩa các phương thức của lớp. Thủ thuật cú pháp này sẽ mở ra siêu kính của lớp hiện tại và tương tác trực tiếp với nó.

class User
  class << self
    self # => #<Class:User>
 
    def all
      [new("Thijs"), new("Robert"), new("Tom")]
    end
  end
end
 
User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]

Ví dụ này tạo một phương thức lớp có tên User.all bằng cách thêm một phương thức vào User của metaclass. Thay vì chuyển một cách rõ ràng một bộ thu cho phương thức như chúng ta đã thấy trước đây, chúng ta đặt self tới User siêu kính của thay vì User chính nó.

Như chúng ta đã học trước đây, bất kỳ định nghĩa phương thức nào không có bộ thu rõ ràng sẽ được thêm vào như một phương thức thể hiện của lớp hiện tại. Bên trong khối, lớp hiện tại là User metaclass của (#<Class:User> ).

instance_eval

Một tùy chọn khác là sử dụng instance_eval , thực hiện điều tương tự với một điểm khác biệt chính. Mặc dù siêu lớp của lớp nhận các phương thức được xác định trong khối, self vẫn là một tham chiếu đến lớp chính.

class User
  instance_eval do
    self # => User
 
    def all
      [new("Thijs"), new("Robert"), new("Tom")]
    end
  end
end
 
User.all # => [#<User:0x00007fb01701efb8 @name="Thijs">, #<User:0x00007fb01701ef68 @name="Robert">, #<User:0x00007fb01701ef18 @name="Tom">]

Trong ví dụ này, chúng tôi xác định một phương thức phiên bản trên User Metaclass giống như trước đây, nhưng self vẫn trỏ tới User . Mặc dù nó thường trỏ đến cùng một đối tượng, nhưng "xác định mặc định" và self có thể trỏ đến các đối tượng khác nhau.

Những gì chúng tôi đã học được

Chúng ta đã biết rằng các lớp là đối tượng duy nhất có thể có các phương thức và các phương thức thể hiện đó thực sự là các phương thức trên siêu kính của một đối tượng. Chúng tôi biết rằng class << self chỉ cần hoán đổi self xung quanh để cho phép bạn xác định các phương thức trên siêu kính và chúng tôi biết rằng instance_eval hầu hết đều giống nhau (nhưng không chạm vào self ).

Mặc dù bạn sẽ không làm việc rõ ràng với kính thiên văn, nhưng Ruby sử dụng chúng một cách rộng rãi. Biết điều gì sẽ xảy ra khi bạn định nghĩa một phương thức có thể giúp bạn hiểu tại sao Ruby lại hoạt động như nó (và tại sao bạn phải đặt tiền tố các phương thức lớp bằng self. ).

Cảm ơn vì đã đọc. Nếu bạn thích những gì bạn đọc, bạn có thể đăng ký Ruby Magic để nhận e-mail khi chúng tôi xuất bản một bài báo mới mỗi tháng một lần.