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.new
và Logging
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#new
và Class#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.