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 end
Or 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 end
Usage
// 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: true
More 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: light
Multi-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!
Created at
2024-09-21 15:13:53 +0700
Related blogs
One Design Pattern a Week: Week 3
Welcome back to my "One Design Pattern a Week" series!
Try to solve this real problem: Incompatible InterfacesImagine you're developing a large web ap...
Software Engineer
Software Engineer
Design Pattern
Design Pattern
2024-09-21 15:53:29 +0700
How Google achieves seamless SSO across multiple domains like Gmail and Youtube?
Hey there! Ever wondered how you can log into Gmail and then magically find yourself logged into YouTube, Google Drive, and all other Google services ...
Software Engineer
Software Engineer
2024-09-24 22:52:06 +0700