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

Thay đổi cách Ruby tạo đối tượng

Một trong những điều làm cho Ruby trở nên tuyệt vời là chúng ta có thể tùy chỉnh hầu hết mọi thứ theo nhu cầu của mình. Điều này vừa hữu ích vừa nguy hiểm. Thật dễ dàng để tự bắn vào chân mình, nhưng khi sử dụng cẩn thận, điều này có thể dẫn đến các giải pháp khá mạnh mẽ.

Tại Ruby Magic, chúng tôi nghĩ rằng hữu ích và nguy hiểm là một sự kết hợp tuyệt vời. Hãy xem cách Ruby tạo và khởi tạo các đối tượng và cách chúng ta có thể sửa đổi hành vi mặc định.

Khái niệm cơ bản về tạo đối tượng mới từ các lớp

Để bắt đầu, hãy xem cách tạo các đối tượng trong Ruby. Để tạo một đối tượng mới (hoặc trường hợp ), chúng tôi gọi là new trên lớp. Không giống như các ngôn ngữ khác, new không phải là một từ khóa của chính ngôn ngữ, mà là một phương thức được gọi giống như bất kỳ phương thức nào khác.

class Dog
end
 
object = Dog.new

Để tùy chỉnh đối tượng mới được tạo, có thể chuyển các đối số vào new phương pháp. Bất cứ thứ gì được truyền dưới dạng đối số, sẽ được chuyển đến trình khởi tạo.

class Dog
  def initialize(name)
    @name = name
  end
end
 
object = Dog.new('Good boy')

Một lần nữa, không giống như các ngôn ngữ khác, trình khởi tạo trong Ruby cũng chỉ là một phương thức thay vì một số cú pháp hoặc từ khóa đặc biệt.

Với ý nghĩ đó, không nên làm lung tung với các phương thức đó, giống như có thể xảy ra với bất kỳ phương thức Ruby nào khác? Tất nhiên là được!

Sửa đổi hành vi của một đối tượng

Giả sử chúng ta muốn đảm bảo rằng tất cả các đối tượng của một lớp cụ thể sẽ luôn in các câu lệnh nhật ký, ngay cả khi phương thức bị ghi đè trong các lớp con. Một cách để làm điều này là thêm một mô-đun vào lớp singleton của đối tượng.

module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end
 
class Bird
  def make_noise
    puts "Chirp, chirp!"
  end
end
 
object = Bird.new
object.singleton_class.include(Logging)
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

Trong ví dụ này, một Bird đối tượng được tạo bằng Bird.newLogging mô-đun được bao gồm trong đối tượng kết quả bằng cách sử dụng lớp singleton của nó.

Lớp Singleton là gì?

Ruby cho phép các phương thức duy nhất cho một đối tượng. Để hỗ trợ điều này, Ruby thêm một lớp ẩn danh giữa đối tượng và lớp thực tế của nó. Khi các phương thức được gọi, các phương thức được định nghĩa trên lớp singleton sẽ được ưu tiên hơn các phương thức trong lớp thực tế. Các lớp singleton này là duy nhất cho mọi đối tượng, vì vậy việc thêm các phương thức vào chúng không ảnh hưởng đến bất kỳ đối tượng nào khác của lớp thực tế. Tìm hiểu thêm về các lớp và đối tượng trong hướng dẫn Lập trình Ruby.

Sẽ hơi rườm rà khi sửa đổi lớp singleton của mỗi đối tượng bất cứ khi nào nó được tạo. Vì vậy, hãy di chuyển bao gồm Logging lớp vào trình khởi tạo để thêm nó cho mọi đối tượng đã tạo.

module Logging
  def make_noise
    puts "Started making noise"
    super
    puts "Finished making noise"
  end
end
 
class Bird
  def initialize
    singleton_class.include(Logging)
  end
 
  def make_noise
    puts "Chirp, chirp!"
  end
end
 
object = Bird.new
object.make_noise
# Started making noise
# Chirp, chirp!
# Finished making noise

Mặc dù điều này hoạt động tốt, nhưng nếu chúng ta tạo một lớp con của Bird , như Duck , trình khởi tạo của nó cần gọi super để giữ lại Logging hành vi. Mặc dù người ta có thể tranh luận rằng luôn luôn là một ý kiến ​​hay nếu gọi đúng super bất cứ khi nào một phương thức bị ghi đè, hãy cố gắng tìm một cách không yêu cầu nó.

Nếu chúng ta không gọi super từ lớp con, chúng tôi mất việc bao gồm Logger lớp:

class Duck < Bird
  def initialize(name)
    @name = name
  end
 
  def make_noise
    puts "#{@name}: Quack, quack!"
  end
end
 
object = Duck.new('Felix')
object.make_noise
# Felix: Quack, quack!

Thay vào đó, hãy ghi đè Bird.new . Như đã đề cập trước đây, new chỉ là một phương thức được thực hiện trên các lớp. Vì vậy, chúng tôi có thể ghi đè nó, gọi super và sửa đổi đối tượng mới được tạo theo nhu cầu của chúng tôi.

class Bird
  def self.new(*arguments, &block)
    instance = super
    instance.singleton_class.include(Logging)
    instance
  end
end
 
object = Duck.new('Felix')
object.make_noise
# Started making noise
# Felix: Quack, quack!
# Finished making noise

Nhưng, điều gì sẽ xảy ra khi chúng ta gọi make_noise trong trình khởi tạo? Rất tiếc, vì lớp singleton không bao gồm Logging mô-đun, chúng tôi sẽ không nhận được đầu ra mong muốn.

May mắn thay, có một giải pháp:Có thể tạo .new mặc định hành vi từ đầu bằng cách gọi allocate .

class Bird
  def self.new(*arguments, &block)
    instance = allocate
    instance.singleton_class.include(Logging)
    instance.send(:initialize, *arguments, &block)
    instance
  end
end

Gọi allocate trả về một đối tượng mới, chưa được khởi tạo của lớp. Vì vậy, sau đó, chúng tôi có thể bao gồm hành vi bổ sung và chỉ sau đó, gọi initialize phương thức trên đối tượng đó. (Vì initialize là riêng tư theo mặc định, chúng tôi phải sử dụng send cho điều này).

Sự thật về Class#allocate

Không giống như các phương pháp khác, không thể ghi đè allocate . Ruby không sử dụng phương thức điều phối thông thường để allocate trong nội bộ. Do đó, chỉ cần ghi đè allocate mà không ghi đè new không hoạt động. Tuy nhiên, nếu chúng ta đang gọi allocate trực tiếp, Ruby sẽ gọi phương thức được xác định lại. Tìm hiểu thêm về Class#newClass#allocate trong tài liệu của Ruby.

Tại sao chúng tôi làm điều này?

Cũng như nhiều thứ khác, việc sửa đổi cách Ruby tạo các đối tượng từ các lớp có thể nguy hiểm và mọi thứ có thể bị hỏng theo những cách không mong muốn.

Tuy nhiên, có những trường hợp sử dụng hợp lệ để thay đổi việc tạo đối tượng. Ví dụ:ActiveRecord sử dụng allocate với một init_from_db khác phương pháp để thay đổi quá trình khởi tạo khi tạo các đối tượng từ cơ sở dữ liệu thay vì xây dựng các đối tượng chưa được lưu. Nó cũng sử dụng allocate để chuyển đổi bản ghi giữa các kiểu kế thừa bảng đơn khác nhau với becomes .

Quan trọng nhất, bằng cách chơi xung quanh việc tạo đối tượng, bạn sẽ có cái nhìn sâu sắc hơn về cách nó hoạt động trong Ruby và mở rộng tâm trí của bạn với các giải pháp khác nhau. Chúng tôi hy vọng bạn thích bài viết.

Chúng tôi rất muốn nghe về những thứ bạn đã triển khai bằng cách thay đổi cách tạo đối tượng mặc định của Ruby. Xin đừng ngần ngại tweet suy nghĩ của bạn lên @AppSignal.