位置:首页 > Ruby > Ruby设计模式编程中单例模式的应用详解

Ruby设计模式编程中单例模式的应用详解

时间:2026-08-17  |  作者:白桃企划师  |  阅读:0

单例模式可以说是设计模式里最基础、也最常用的一种。它的核心目标很明确:让某个类在系统中只存在一个实例,并且这个实例能被全局访问

这个概念听起来简单,但实现时有不少细节需要注意。

简介

说白了,单例模式就是要把类的实例化过程控制住,不让外部随意new对象。

通常的做法是让类自己管理自己的唯一实例,对外提供一个静态方法(类方法)来获取这个实例。

为什么必须是静态方法?因为如果用一个实例去生成另一个唯一实例,本身就是矛盾的。你还没拿到唯一的那个实例,自然也无法依赖它去创建对象。

详解Ruby设计模式编程中对单例模式的运用

要点

单例模式要满足三个条件:

  • 类只能有一个实例
  • 这个实例必须由类自己来创建
  • 它必须自己把这个实例提供给整个系统

从实现角度来说,对应的就是:

  • 私有化构造函数(让外部不能直接new)
  • 在类内部维护一个静态的私有实例
  • 提供一个公开的静态方法返回这个实例

singleton

先看一个简单的例子,用类变量来模拟单例行为:

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 
 
cv1 = ClassVariableTester.new 
cv1.increment 
cv1.increment 
puts("cv1:#{cv1}") 
cv2 = ClassVariableTester.new 
puts("cv2:#{cv2}") 
 
#cv1:class count :2 -- instance count :2 
#cv2:class count :2 -- instance count :0 

这里有个关键点:类变量 @@class_count 被所有实例共享。

所以当cv1调用了两次increment后,@@class_count 变成2;创建cv2时,这个类变量仍然是2。

而实例变量 @instance_count 只属于当前对象,所以cv2的实例变量是0。

这种类变量的共享特性,其实就是单例模式的一种朴素体现。

但真正的单例模式需要更严格的控制。下面是一个经典实现:

class SimpleLogger 
 
 @@instance = SimpleLogger.new 
  
 def self.get_instance 
  @@instance 
 end 
 
 private_class_method :new 
end 
 
sl1 = SimpleLogger.get_instance 
sl2 = SimpleLogger.get_instance 
puts sl1 == sl2 

输出结果是 true

这里用类变量 @@instance 保存唯一实例,通过类方法 get_instance 返回。

但注意,SimpleLogger.new 仍然能创建新实例。所以必须把 new 方法设为私有,这行代码 private_class_method :new 就是干这个的。

如果尝试直接调用 SimpleLogger.new,会得到:

private method `new' called for SimpleLogger:Class (NoMethodError)

Ruby 标准库提供了一个更简洁的 singleton 模块,几行代码就能搞定:

require 'singleton' 
class SimpleLogger 
 include Singleton 
end 
 
#puts SimpleLogger.new 
sl1 = SimpleLogger.instance 
sl2 = SimpleLogger.instance 
puts sl1 == sl2 

输出仍然是 true

混入 Singleton 之后,类自动获得了类变量、初始化、 instance 方法,并且 new 被设为私有。

通过 SimpleLogger.instance 就能获取唯一的实例。

两种实现方式的区别

这两种方式有个关键区别:

  • 第一种是“勤性单例”(eager instantiation),在类加载时就创建了实例
  • 第二种是“惰性单例”(lazy instantiation),只有在第一次调用 instance 时才创建

不过,Singleton 模块并不是绝对安全的。

你可以通过 public_class_method :newnew 重新设为公开,然后就能直接 SimpleLogger.new 创建新对象了:

class SimpleLogger 
 public_class_method :new 
end 
 
puts SimpleLogger.new 

所以,使用单例模式时,最好在团队中约定好使用方式,避免被轻易绕过。

替代思路

再来看看两种常见的替代思路:

使用全局变量

尽量别用。因为全局变量会让代码耦合得死死的。

其实单例模式本质上和全局变量作用一样,但更优雅。比如 $logger = SimpleLogger.new 这种写法,虽然简单,但破坏了封装。

使用类本身作为单例

把类方法直接当作实例方法使用,也能达到类似效果:

class SimpleLogger 
  
 WARNING = 1 
 INFO = 2 
 
 def initialize(file) 
  @@log = File.open(file, "w") 
  @@level = WARNING 
 end 
  
 
 def self.warning(msg) 
  puts @@level > WARNING 
  @@log.puts(msg) if @@level > WARNING 
  @@log.flush 
 end 
 
 def self.level 
  @@level 
 end 
 
 def self.level=(new_level) 
  @@level = new_level 
 end 
  
end 
SimpleLogger.new("test.txt") 
puts SimpleLogger.level 
SimpleLogger.level = SimpleLogger::INFO 
puts SimpleLogger.level 
SimpleLogger.warning("warning") 

这种方式下,所有方法都是类方法,数据存储在类变量中,本质上也是单例。

实例

最后看一个实际应用场景:用单例模式管理浏览器自动化测试的实例

require 'rubygems'
require 'watir'
require 'singleton'
class AutoTest
 include Singleton
 def OpenUrl(url)
  @browser= Watir::Browser.new
  @browser.goto(url)
  @url=url
 end
 def set_textarea(text)
  @browser.text_field(:id,'kw').set(text)
 end
 def click
  @browser.button(:id,'su').click
 end
end
test,test2 = AutoTest.instance
test.OpenUrl('http://www.baidu.com')
test.set_textarea('aslandhu')
test.click

注意这里虽然写了 test, test2 = AutoTest.instance,但第二个变量实际上接收的是 nil

因为 instance 方法只会返回同一个实例,所以 test2 并没有成功创建。

再看另一种写法,试图用类方法直接操作单例:

require 'rubygems'
require 'watir'
require 'singleton'
require 'thread'
class TestOneObj
 
end
class <

这段代码试图创建两个 Browser 对象,但事实上 testtest2 是同一个对象。

虽然可能会打开两个浏览器窗口,因为每次调用 instance 都执行了 Watir::Browser.new,但 Ruby 对象的引用是同一个。

testtest2 指向同一个实例。

这提醒我们:单例模式控制的是对象的创建,而不是底层资源的重复创建。具体实现时,需要把边界考虑清楚。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多