0%
Reading Settings
Font Size
18px
Line Height
1.5
Letter Spacing
0.01em
Font Family
Table of contents
One Design Pattern a Week: Week 1
Software Engineer
Software Engineer
Design Pattern
Design Pattern
Welcome back to my "One Design Pattern a Week" series!
Try to solve this real problem: Configuration Management
Imagine you're developing a large web application. This application requires various configuration settings such as database credentials, API keys, and feature toggles. These settings should be loaded once (because it takes time) and be accessible throughout the application.
Here are some potential issues:
Here are some potential issues:
- Multiple Instances: If the configuration settings are loaded in multiple places, it can lead to inconsistencies and increased memory usage.
- Global Access: You need a way to access these settings globally without passing them around as parameters.
Take a moment to think about how you would solve this problem. How can you ensure that there's only one instance of the configuration manager accessible throughout your application?
Singleton Pattern
The Singleton pattern is a creational design pattern that addresses the issues mentioned above. It ensures that a class has only one instance and provides a global point of access to it. This is particularly useful when exactly one object is needed to coordinate actions across the system.
Key Characteristics:
Key Characteristics:
- Single Instance: Only one instance of the class is created.
- Global Access Point: Provides a way to access the instance globally.
- Lazy Initialization: The instance is created only when it is needed.
Implementing Singleton in Ruby
// language: ruby
class ConfigurationManager
@instance = nil
private_class_method :new # Prevent user from using ConfigurationManager.new
def self.instance
@instance ||= new # Only call new when instance is nil
end
def initialize
@settings = {}
end
def load_settings(file)
# Simulate loading settings from a file
@settings = { db: 'localhost', api_key: '12345' }
puts "Settings loaded from #{file}"
end
def get_setting(key)
@settings[key]
end
endOr you can use Ruby's built-in module called 'singleton', which makes it easy to implement the Singleton pattern.
// language: ruby
require 'singleton'
class ConfigurationManager
include Singleton
def initialize
@settings = {}
end
def load_settings(file)
@settings = { db: 'localhost', api_key: '12345' }
puts "Settings loaded from #{file}"
end
def get_setting(key)
@settings[key]
end
endUsage
// language: ruby
config_manager = ConfigurationManager.instance
config_manager.load_settings('config.yml')
another_config_manager = ConfigurationManager.instance
puts another_config_manager.get_setting(:db) # Output: localhost
# Both instances are the sameputs
config_manager.equal?(another_config_manager) # Output: trueMore Practical Examples
1. Logger System
A logging system often needs a single point of access to write log entries consistently across the application.
A logging system often needs a single point of access to write log entries consistently across the application.
// language: ruby
require 'singleton'
class Logger
include Singleton
def initialize
@log_file = File.open('application.log', 'a')
end
def log(message)
@log_file.puts(message)
@log_file.flush
end
end
# Usage
logger = Logger.instance
logger.log('This is a log message.')2. Cache Manager
A cache manager can maintain a single instance to store frequently accessed data, reducing redundant data fetching.
// language: ruby
require 'singleton'
class CacheManager
include Singleton
def initialize
@cache = {}
end
def store(key, value)
@cache[key] = value
end
def fetch(key)
@cache[key]
end
end
# Usage
cache = CacheManager.instancecache.store('user_1', { name: 'John Doe', age: 30 })
another_cache = CacheManager.instance
puts another_cache.fetch('user_1') # Output: {:name=>"John Doe", :age=>30}3. Application Settings
A single point to manage and retrieve application-wide settings.
// language: ruby
require 'singleton'
class AppSettings
include Singleton
def initialize
@settings = { theme: 'dark', language: 'en' }
end
def get_setting(key)
@settings[key]
end
def set_setting(key, value)
@settings[key] = value
end
end
# Usage
settings = AppSettings.instance
puts settings.get_setting(:theme) # Output: dark
settings.set_setting(:theme, 'light')
puts settings.get_setting(:theme) # Output: lightMulti-Thread/Process Applications
In a multi-threaded or multi-process environment, ensuring that only one instance of a Singleton is created can be challenging. Here's how you can handle it in Ruby:
Multi-Threaded Applications
To make the singleton thread safe, you can use Ruby's built-in Mutex class to synchronize access to the instance creation.
// language: ruby
require 'singleton'
require 'thread'
class ThreadSafeSingleton
@instance = nil
@mutex = Mutex.new
private_class_method :new
def self.instance
return @instance if @instance
@mutex.synchronize do
@instance ||= new
end
@instance
end
def initialize
# initialization code
end
end
# Usage in a multi-threaded environment
threads = 10.times.map do
Thread.new do
singleton = ThreadSafeSingleton.instance
puts singleton.object_id
end
end
threads.each(&:join)In this implementation, I use a Mutex to ensure that only one thread can execute the instance creation code at a time. This prevents race conditions where multiple threads might attempt to create an instance simultaneously.
Multi-Process Applications
Handling Singleton in a multi-process environment can be tricky because each process has its own memory space, meaning that the Singleton instance in one process won't be the same as in another.
However, there are strategies to manage a Singleton-like behavior across processes. However, I don't include these strategies in this post because they come with their own set of complexities and potential pitfalls
However, there are strategies to manage a Singleton-like behavior across processes. However, I don't include these strategies in this post because they come with their own set of complexities and potential pitfalls
Conclusion
The Singleton pattern is a powerful tool for managing shared resources, ensuring only one instance of a class exists, and providing a global access point to that instance.
Happy coding!
Happy coding!
Related blogs
Speed Up Independent Queries Using Rails load_async
When you're building a dashboard, it's common to fetch multiple, independent datasets. The page loading might be slow because it has to fetch all data to render a page. A common solution is using AJAX to load pieces of the dashboard, which is great, ...
Software Engineer
Software Engineer
Hello Golang: My First Steps to the Language
I’ve worked with Ruby in several projects, which is defined as "a programmer’s best friend" for its clean and English-like syntax. While my back-end experience is rooted in the Ruby on Rails framework, I prefer TypeScript for building CLI tools and s...
Software Engineer
Software Engineer