[Series-DesignPatternInRuby] Singleton - Phần 1
Bài đăng này đã không được cập nhật trong 7 năm
Mở đầu
Chào mọi người, đây là bài đầu tiên trong series DesignPatternInRuby mà mình sẽ dịch từ cuốn Design Pattern in Ruby (2007) Trong series này mình sẽ cố gắng dịch toàn bộ cuốn sách, cố gắng 1 tuần có ít nhất 1 bài. Mong mọi người ủng hộ.
Making sure there is only One with the Singleton
Ngay cả khi những coders biết rất ít về các mẫu design pattern (DP) thì cũng biết về Singleton
. Phần lớn họ đều biêt 1 thứ:
Singleton are Bad, with a capital "B"
Singleton
xuất hiện ở mọi nơi. Trong Java
nó xuất hiện trong hầu hết những thứ xung quanh ngôn ngữ này: tomcat
, ant
, JDOM
. Trong Ruby chúng ta có thể tìm nó ở Webrick
, rake
, và ngay cả trong Rails
. Vậy Singleton
là gì mà nó lại trở nên cần thiết nhưng cũng lại bị ghét đến vậy. Trong chương này chúng ta sẽ tìm hiểu tại sao lại cần Singleton
, học cách tạo nên một Singleton
trong Ruby
, tại sao Singleton
lại có thể gây rắc rối, và làm cách nào để tránh những rắc rối mà nó mang lại.
One Object, Global Access
Ý tưởng của Singleton
rất đơn giản: Nếu project của bạn có một số thứ "duy nhất", chỉ có duy nhất một file config
, hay một log file
duy nhất. Hoặc có thể bạn làm việc với 1 màn hình duy nhất, hay dữ liệu trong chương trình được nhập duy nhất từ một bàn phím. Nhiều thành phần cũng truy cập vào một DB
duy nhất. Class
mà bạn đang viết sinh ra một instance variable
mà nhiều nơi trong chương trình dùng đến, và bạn cảm thấy việc truyền instance variable
đó đến một đống method khác nhau trông thật ngốc nghếch.
Singleton
được sinh ra để giải quyết những vấn đề kể trên. Gang of Four (GOF) gợi ý hãy sử dụng Singleton
để giải quyết những vấn đề:
A class that can have only one instance and that provides global access to that one instance.
Có rất nhiều cách khác nhau để tìm hiểu về Singleton
trong Ruby
, nhưng chúng ta sẽ bắt đầu với một method được recommend bởi GOF:
Giao quyền quản lý việc tạo và truy cập đến Singleton object cho class của nó.
Hãy cùng xem lại một số kiến thức về class variable
và class methods
trong Ruby
.
Class Variables and Methods
Class variable
Class variable
là biến mà nó được "đính kèm" với class
, chứ không phải là instance
như instance variable
. Việc khai báo class variable
trong Ruby
rất đơn giản: chỉ cần thêm 2 kí tự @
trước tên biến. Ví dụ như class
sau:
class ClassVariableTester
@@class_count = 0
def initialize
@instance_count = 0
end
def increment
@@class_count = @class_count + 1
@instance_count = @instance_count + 1
end
def to_s
"class_count: #{@class_count} instance_count: #{instance_count}"
end
end
Giờ hãy tạo instance của class ClassVariableTester
mà chúng ta viết ở trên:
c1 = ClassVariableTester.new
c1.increment
c1.increment
p c1
Không có gì quá bất ngờ, kết quả như sau:
class_count: 2 instance_count: 2
Nhưng chuyện gì sẽ xảy ra trong trường hợp chúng ta tạo thêm một instance
c2
cho class ClassVariableTester
?
c2 = ClassVariableTester.new
p c2
Kết quả nhận được:
class_count: 2 instance_count: 0
Chuyện gì đã xảy ra vậy? tại sao instance_count
bị "reset" về 0
, nhưng class_count
vẫn đếm đúng số instance variable
đã được tạo ra?
Class methods
Việc tạo ra class-level
method trong Ruby hơi khó khăn hơn chút xíu, nhưng vẫn rất dễ dàng.
class SomeClass
def self.class_level_method
p "hello from class class method"
end
end
Giờ chúng ta đã có thể gọi class methods
đó từ class
SomeClass.class_level_method
Nếu bạn không muốn dùng từ khóa self
, Ruby
cung cấp công cụ khác cho bạn.
class SomeClass
def SomeClass.class_level_method
p "hello from class method"
end
end
A First Try at a Ruby Singleton
Bây giờ chúng ta đã biết cách để tạo ra một class variable và method
, vậy là đủ công cụ để tạo ra một Singleton object
trong Ruby
. Giả sử chúng ta cần implement
một class
dùng cho việc logging
như sau:
class SimpleLogger
attr_accessor :level
ERROR = 1
WARNING = 2
INFO = 3
def initialize
@log = File.open("log.txt", "w")
@level = WARNING
end
def error msg
@log.puts msg
@log.flush
end
def warning msg
@log.puts msg if @level >= INFO
@log.flush
end
end
Đầu tiên chúng ta sẽ xem xét cách viết không sử dụng Singleton
như trên.
logger = SimpleLogger.new
logger.level = SimpleLogger::INFO
logger.info "Doing the first thing"
logger.info "Now doing the second thing"
Managing the Single Instance
Điểm quan trọng nhất của Singleton
là tránh việc truyền các object
như logger trên đi khắp chương trình. Thay vào đó, chúng ta sẽ giao trách nhiệm quản lý object
đó cho class
SimpleLogger
. Vậy làm sao chúng ta có thể chuyển class
trên trở thành một Singleton class
?
Đầu tiên, chúng ta sẽ tạo một biến để giữ instance duy nhất mà class Singleton
cần quản lý. Bạn cần một method
để trả về instance
duy nhất đó lúc cần.
class SimpleLogger
@@instance = SimpleLogger.new
def self.instance
@@instance
end
end
Và bây giờ, mỗi khi gọi SimpleLogger.instance
, chúng ta đều sẽ nhận được duy nhất một instance
duy nhất của SimpleLogger
logger1 = SimpleLogger.instance #=> return the logger
logger2 = SimpleLogger.instance #=> return the same logger
Và giờ đây, chúng ta có thể sử dụng singleton
logger ở mọi nơi trong code của mình.
SimpleLogger.instance.info "Computer win chess game"
SimpleLogger.instance.warning "AE-35 hardware failure predicted"
SimpleLogger.instance.error "HAL-9000 malfunction, take emergency action!"
Making Sure There Is Only One
Hãy nhớ rằng, một yêu cầu tối quan trọng của singleton
là chỉ có duy nhất một và chỉ một instance
của class Singleton
. Đoạn code định nghĩa SimleLogger
của chúng ta phía trên có vấn đề. Chúng ta vừa có thể lấy ra 1 instance
bằng SimpleLogger.instance
lại vừa có thể bằng SimpleLogger.new
, vì vậy chúng ta cần chỉnh sửa lại một chút bằng cách đưa method new
trở thành private
:
class SimpleLogger
@@instance = SimpleLogger.new
def self.instance
@@instance
end
private_class_method :new
end
The Singleton Module
Class SimpleLogger
của chúng ta đã thỏa mãn điều kiện của GOF: chỉ tồn tại duy nhất một instance
, nó có thể cung cấp 1 instance
để sử dụng bất cứ lúc nào chúng ta cần, không bất kì ai được phép tạo instance
thứ 2.
Nhưng vấn đề vẫn chưa dừng lại ở đó, sẽ ra sao nếu chúng ta muốn tạo ra một class Singleton
thứ 2, thứ 3? Điều đó dẫn đến việc trùng lặp code không cần thiết. Vậy phải giải quyết như thế nào?
Ruby đưa ra một phương pháp đơn giản, ít nhức đầu hơn. Chỉ cần include Singleton module
:
require "singleton"
class SimpleLogger
include Singleton
end
và module Singleton
đã làm tất cả những gì mà chúng ta cần: tạo ra class method
: instance
, đưa new
trở thành private method
. Việc sử dụng là hoàn toàn tương tự: SimpleLogger.instance
Lazy and Eager Singleton
Có một điểm khác biệt lớn nhất giữa việc chúng ta tự viết Singleton class
và việc sử dụng module Singleton
. Hãy xem lại code chúng ta tự viết lúc đầu:
class SimpleLogger
@@instance = SimpleLogger.new
end
Chúng ta có thể nhận thấy, instance
của SimpleLogger
được tạo trước khi sử dụng, nói cách khác, mặc dù chưa gọi SimpleLogger.instance
nhưng instance
của SimpleLogger
đã được khởi tạo giá trị.
Việc sử dụng module Singleton
thì khác, nó đợi đến khi SimpleLogger.instance
được gọi thì instance
mới được khởi tạo. Kỹ thuật này gọi là lazy instantiation
To be continued...
All rights reserved