Một điều làm Ruby tuyệt vời là customize gần như mọi thứ theo nhu cầu. Cả hữu ích và nguy hiểm. Dễ bắn mình vào chân, nhưng dùng cẩn thận tạo ra powerful solutions.
Tại Ruby Magic, chúng tôi nghĩ useful và dangerous là combo tuyệt vời. Hãy xem Ruby tạo và initialize object như thế nào và modify default behavior như thế nào.
Basics of Creating New Objects from Classes
Bắt đầu, xem tạo object trong Ruby. Tạo object (hoặc instance), gọi new trên class. Khác ngôn ngữ khác, new không phải keyword ngôn ngữ, mà method gọi như method khác.
class Dog
end
object = Dog.new
Để customize object mới tạo, pass argument cho new. Cái gì pass as argument, sẽ pass cho initializer.
class Dog
def initialize(name)
@name = name
end
end
object = Dog.new('Good boy')
Lại khác ngôn ngữ khác, initializer trong Ruby cũng chỉ là method thay vì special syntax/keyword.
Với điều đó, không nên possible mess around với những method đó, như với bất kỳ Ruby method khác? Tất nhiên!
Modify Behavior of Single Object (Sửa behavior single object)
Giả sử muốn ensure tất cả object của class cụ thể luôn print log statements, ngay cả khi method override trong subclass. Cách là thêm module vào singleton class của object.
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
Ví dụ này, Bird object tạo bằng Bird.new, module Logging include vào object kết quả dùng singleton class.
Singleton Class là gì?
Ruby cho phép method unique cho single object. Để support, Ruby thêm anonymous class giữa object và actual class. Khi method gọi, method định nghĩa trên singleton class có precedence hơn method actual class. Singleton classes unique cho mỗi object, nên thêm method vào không affect object khác của actual class. Tìm hiểu thêm về class và object trong Programming Ruby guide.
Hơi cumbersome modify singleton class mỗi object khi tạo. Move inclusion Logging class vào initializer để thêm cho mọi object tạo ra.
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
Cách này work tốt, nhưng nếu tạo subclass Bird như Duck, initializer của nó cần gọi super để retain Logging behavior. Mặc dù có thể argue luôn good idea gọi super khi override method, hãy tìm cách không require nó.
Nếu không gọi super từ subclass, mất inclusion Logger class:
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 đó, override Bird.new. Như nói, new chỉ là method implement trên class. Vậy override nó, gọi super, modify object mới tạo theo nhu cầu.
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, nếu gọi make_noise trong initializer? Thật không may, vì singleton class chưa include Logging module, ta không get desired output.
May mắn có solution: Có thể tạo default .new behavior từ đầu bằng 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ề object mới, uninitialized của class. Sau đó include additional behavior, sau đó mới gọi initialize trên object đó. (Vì initialize private mặc định, phải dùng send).
Sự thật về Class#allocate
Khác method khác, không thể override allocate. Ruby không dùng conventional method dispatch cho allocate internally. Kết quả, chỉ override allocate không override new không work. Tuy nhiên, nếu gọi allocate trực tiếp, Ruby sẽ gọi redefined method. Tìm hiểu thêm Class#new và Class#allocate trong Ruby documentation.
Tại sao làm điều này?
Như nhiều thứ, modify cách Ruby tạo object từ class có thể dangerous và things break unexpected ways.
Tuy nhiên, có valid use cases thay đổi object creation. Ví dụ, ActiveRecord dùng allocate với init_from_db khác để thay đổi initialization process khi tạo object từ database so với build unsaved object. Cũng dùng allocate convert records giữa single-table inheritance types với becomes.
Quan trọng nhất, chơi với object creation cho deeper insight cách nó work trong Ruby và mở mind cho different solutions. Hy vọng bạn enjoy bài viết.
Chúng tôi muốn nghe những gì bạn implement bằng thay đổi default way Ruby tạo object. Đừng ngần ngại tweet thoughts @AppSignal.