Table of Contents
Item 1: Ruby Best Practices - Consider static factory methods instead of constructors
Introduction to Static Factory Methods in [[Ruby]]
In Ruby, object creation typically involves using constructors, specifically the `initialize` method within a class. However, static factory methods offer an alternative that can provide additional flexibility, readability, and maintainability. By implementing static factory methods, you can encapsulate object creation logic, enforce specific rules, and even return instances from a pool or cache, all while maintaining a clear and expressive codebase.
Why Consider Static Factory Methods Instead of Constructors?
Static factory methods in Ruby offer several advantages over traditional constructors: 1. **Improved Naming**: Static factory methods can have descriptive names, making the purpose of the method clear and the code more readable. 2. **Control Over Instantiation**: Static factory methods can control how instances are created, including returning existing instances or subtypes, enhancing flexibility and efficiency. 3. **Encapsulation**: Static factory methods can encapsulate complex object creation logic, keeping the class’s constructor simple and focused.
Example 1: Basic Constructor vs. Static Factory Method
- Basic Constructor Using `initialize`
```ruby class Person
attr_reader :name, :age
def initialize(name, age) @name = name @age = age endend
john = Person.new(“John Doe”, 30) ```
- Static Factory Method
```ruby class Person
attr_reader :name, :age
private_class_method :new # Make the constructor private
def self.create(name, age) return nil if age < 0 # Validation logic new(name, age) end
private
def initialize(name, age) @name = name @age = age endend
john = Person.create(“John Doe”, 30) ```
In this example, the `create` method serves as a static factory method, controlling the instantiation of `Person` objects and allowing for validation before an object is created.
Example 2: Returning Cached Instances
- Traditional Constructor Approach
```ruby class Logger
attr_reader :level
def initialize(level) @level = level endend
logger1 = Logger.new(“INFO”) logger2 = Logger.new(“INFO”) ```
- Static Factory Method with Caching
```ruby class Logger
attr_reader :level
@@instances = {}
private_class_method :new
def self.get_logger(level) @@instances[level] ||= new(level) end
private
def initialize(level) @level = level endend
logger1 = Logger.get_logger(“INFO”) logger2 = Logger.get_logger(“INFO”) ```
In this example, `get_logger` is a static factory method that returns a cached instance of `Logger` if it already exists, avoiding the creation of duplicate objects.
Example 3: Subtype Selection
- Traditional Constructor Approach
```ruby class Shape
attr_reader :type, :size
def initialize(type, size) @type = type @size = size endend ```
- Static Factory Method with Subtype Selection
```ruby class Shape
private_class_method :new
def self.create_shape(type, size) case type when "Circle" Circle.new(size) when "Square" Square.new(size) else raise "Unknown shape type" end endend
class Circle < Shape
def initialize(radius) @radius = radius endend
class Square < Shape
def initialize(side) @side = side endend
circle = Shape.create_shape(“Circle”, 10) square = Shape.create_shape(“Square”, 5) ```
In this example, `create_shape` is a static factory method that determines which subtype (`Circle` or `Square`) to create based on the `type` argument, providing flexibility in object creation.
Example 4: Creating Immutable Objects
- Traditional Constructor Approach
```ruby class Account
attr_reader :balance
def initialize(balance) @balance = balance endend
account = Account.new(100) ```
- Static Factory Method for Immutability
```ruby class Account
attr_reader :balance
private_class_method :new
def self.create(balance) new(balance.freeze) end
private
def initialize(balance) @balance = balance endend
account = Account.create(100) ```
In this example, `create` is a static factory method that ensures the `balance` is frozen, creating an immutable `Account` object.
When to Consider Static Factory Methods in [[Ruby]]
Static factory methods are particularly useful in the following scenarios: - **Complex Object Initialization**: When object creation involves validation, caching, or complex initialization logic, static factory methods provide a clean and maintainable solution. - **Immutability**: For creating immutable objects, static factory methods can enforce immutability while maintaining clarity and simplicity. - **Subtype Creation**: When different subtypes of an object need to be created based on input parameters, static factory methods offer a flexible and extensible approach.
Conclusion
In Ruby, static factory methods offer a powerful alternative to traditional constructors, providing greater control over object creation and improving code readability and maintainability. By using static factory methods, you can encapsulate complex logic, implement caching, and enforce object immutability, making your code more robust and easier to manage. This approach aligns with modern Ruby development practices, where flexibility and clarity are key considerations.