12/08/2018, 13:06

Design Pattern - Singleton

Singleton Singleton có lẽ là một trong những pattern dễ hiểu nhất. Thậm chí ngay cả người ít biết về Design Pattern cũng biết về singleton. Nhưng họ chủ yếu chỉ biết một điều: Singletons are Bad. Singleton đảm bảo rằng một class có duy nhất một thực thể (instance) hay đối tượng (object) và cung ...

Singleton

Singleton có lẽ là một trong những pattern dễ hiểu nhất. Thậm chí ngay cả người ít biết về Design Pattern cũng biết về singleton. Nhưng họ chủ yếu chỉ biết một điều: Singletons are Bad.

Singleton đảm bảo rằng một class có duy nhất một thực thể (instance) hay đối tượng (object) và cung cấp một cách thức truy nhập toàn cục để có thể từ bất kỳ đâu cũng lấy được instance duy nhất đó. Chúng ta cũng gọi thực thể duy nhất đó là thực thể chính (solely) của class đó.

Đối với một vài class thì việc chỉ có duy nhất một instance (object) là rất quan trọng. Bạn có thấy là các chương trình thường xuyên có một tập tin cấu hình duy nhất không. Nó không phải là bất thường đối với một chương trình cho phép bạn biết làm thế nào nó được thực hiện thông qua một file log duy nhất. Hay Giao diện ứng dụng thường xuyên có một cửa sổ chính, và họ thường lấy đầu vào từ bàn phím một cách chính xác. Hay nhiều ứng dụng khác thì cần phải nói chuyện với một cơ sở dữ liệu chính xác. Nếu bạn chỉ có một instance của một class và rất nhiều mã cần truy cập đến instance đó, thì thật là ngớ ngẩn khi chuyển object từ method này sang method khác.

Làm thế nào để chúng ta đảm bảo một class chỉ có một thể hiện duy nhất và dễ dàng truy nhập được? Một object toàn cục có thể làm cho nó dễ dàng truy nhập được nhưng không ngăn cấm bạn tạo thêm nhiều object khác.

Một giải pháp tốt hơn là làm cho class đó tự bản thân nó có thể điều khiển được thực thể chính của nó. Class đó có thể đảm bảo rằng không có thực thể khác được tạo ra (bằng cách chặn đứng các yêu cầu tạo object mới) và cung cấp cách thức để truy nhập được thực thể chính của nó. Đó là singleton pattern.

Sử dụng Singleton trong Ruby

Có rất nhiều cách để áp dụng Singleton trong Ruby, nhưng chúng ta sẽ bắt đầu từ gợi ý của GOF:

"Let the class of the singleton object manage the creation and access to its sole instance"

tức là "Hãy để class của đối tượng singleton quản lý việc tạo và truy cập tới chính instance của nó".

Để làm việc đó điều đầu tiên ta cần biết là class variables và class methods

Class Variables and Methods

Class variables là một biến được gắn và class thay vì một instance của class. Để tạo biến này cũng khá đơn giản, chỉ cần thêm @ vào trước tên của biến.

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

c1 = ClassVariableTester.new
c1.increment
c1.increment
puts("c1: #{c1}")

##Kết quả là:
c1: class_count: 2 instance_count: 2
##Nhưng nếu
c2 = ClassVariableTester.new
puts("c2: #{c2}")
##Kết quả là:
c2: class_count: 2 instance_count: 0

Class methods: để tạo ra class này, bạn cần biết trong Ruby, trong định nghĩa một class, nhưng không trong định nghĩa một method nào, thì biến self chính là class mà bạn đang định nghĩa. Hãy chạy thử đoạn code sau:

class SomeClass
  puts("Inside a class def, self is #{self}")
end

##Output là:
Inside a class def, self is SomeClass

Qua ví dụ trên bạn có lẽ cũng sẽ tìm thấy cách tạo ra class methods rồi chứ, ta có thể định nghĩa class method như sau:

class SomeClass
  def self.class_level_method
    puts('hello from the class method')
  end
end
##hoặc
class SomeClass
  def SomeClass.class_level_method
    puts('hello from the class method')
  end
end
##hoặc cách sau hay được sử dụng hơn cả
class SomeClass
  class << self
    def class_level_method
      puts('hello from the class method')
    end
  end
end

Ruby Singleton

Để bắt đầu, chúng ta sẽ tạo một non-singleton class và sau đó sẽ chuyển nó sang singleton class.

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 >= WARNING
    @log.flush
  end

  def info(msg)
    @log.puts(msg) if @level >= INFO
    @log.flush
  end
end

logger = SimpleLogger.new
logger.level = SimpleLogger::INFO
logger.info('Doing the first thing')
# Do the first thing...
logger.info('Now doing the second thing')
# Do the second thing...

SimpleLogger là một non-singleton. Làm thế nào để biến SimpleLogger thành một singleton. Đầu tiên, bạn cần giữ một instance duy nhất của class này bằng cách sử dụng class method.

class SimpleLogger
  # Lots of code deleted...
  @@instance = SimpleLogger.new
  def self.instance
    return @@instance
  end
end

##Bạn có thể gọi instance method bao nhiêu lần
##nhưng nó chỉ trả về duy nhất một object logger
logger1 = SimpleLogger.instance # Returns the logger
logger2 = SimpleLogger.instance # Returns exactly the same logger

SimpleLogger.instance.info('Computer wins chess game.')
SimpleLogger.instance.warning('AE-35 hardware failure predicted.')
SimpleLogger.instance.error(
'HAL-9000 malfunction, take emergency action!')

Bây giờ SimpleLogger đã trở thành 1 singleton class nhưng nó vẫn chưa hoàn chỉnh. Bởi vì singleton tức là chỉ có một và duy nhất một instance cho class mà với class SimpleLogger chúng ta vẫn có thể khởi tạo thêm một instance nữa bằng cách gọi SimpleLogger.new. Để hạn chế việc này, ta cần tạo method new trong private

class SimpleLogger
  # Lots of code deleted...
  @@instance = SimpleLogger.new

  def self.instance
    return @@instance
  end

  private_class_method :new
end

Ưu nhược điểm

  • Quản lý việc truy cập tốt hơn vì chỉ có một thể hiện đơn nhất.
  • Cho phép cải tiến lại các tác vụ (operations) và các thể hiện (representation) do pattern có thể được kế thừa và tùy biến lại thông qua một thể hiện của lớp con
  • Quản lý số lượng thể hiện của một lớp, không nhất thiết chỉ có một thể hiện mà có số thể hiện xác định.
  • Khả chuyển hơn so với việc dùng một lớp có thuộc tính là static, vì việc dùng lớp static chỉ có thể sử dụng một thể hiện duy nhất, còn Singleton Pattern cho phép quản lý các thể hiện tốt hơn và tùy biến theo điều kiện cụ thể.

Tham khảo

Github (updating):https://github.com/ducnhat1989/design-patterns-in-ruby

Sách: “DESIGN PATTERNS IN RUBY” của tác giả Russ Olsen

:

  • CÁC NGUYÊN TẮC TRONG DESIGN PATTERN
0