Share This Article
Design patterns in Ruby (as well as in the rest of the languages) are a standard solution to a common problem in software design. They are not specific to any programming language and can be implemented in many different ways.
There are many different design patterns, but some of the most common ones are the following:
- Singleton
- Command
- Observer
- Decorator
- Factory
- MVC
- Adapter
- Iterator
- Proxy
- Wrapper
Don’t worry if you don’t know what all of these are, we will take a look at some of them in more detail in this article.
Ruby Singleton Pattern
The singleton pattern is probably one of the most well-known patterns. It ensures that only one instance of a class is created. This can be useful in situations where you want to limit the number of database connections or to ensure that only one user is logged in at a time.
The singleton pattern can be implemented in many different ways, but common practice is to use a class method to return the same class instance every time it is called.
class MyClass
@@instance = nil
def self.instance
@@instance ||= MyClass.new
end
private_class_method :new
end
This particular implementation uses a class variable to store a single class instance. The first time the instance
method is called, the class variable is set to an instance of the class.
Every subsequent call to instance
will return the same instance.
The private_class_method
call at the end is used to ensure that no one can instantiate the class directly.
Ruby Command Pattern
The command pattern is used to encapsulate a request as an object. This can be useful when you want to be able to queue up requests or when you want to be able to log requests.
The command pattern is often used with the observer pattern to notify observers when a command is executed.
class Command
def execute
raise NotImplementedError
end
end
class LoginCommand < Command
def execute
# login logic
end
end
class LogoutCommand < Command
def execute
# logout logic
end
end
In this example, we have a base Command
class and two subclasses that implement the execute
method.
This execute
method can be called to execute the command.
Ruby Observer Pattern
The observer pattern is used to decouple objects, so they are unaware of each other. This can be useful when you want to be able to add or remove objects at runtime.
The observer pattern is often used with the command pattern to notify observers when a command is executed.
class Subject
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
def remove_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update
end
end
end
class Observer
def update
raise NotImplementedError
end
end
class MyObserver < Observer
def update
# observer logic
end
end
subject = Subject.new
observer = MyObserver.new
subject.add_observer(observer)
subject.notify_observers
In this example, we have a Subject
class and an Observer
class. The Subject
class has methods for adding and removing observers.
Whenever the notify_observers
method is called, it will call the update
method on all of the observers.
The Observer
class is an abstract class and the MyObserver
class is a concrete implementation.
Ruby Decorator Pattern
The decorator pattern is used to add behavior to an object without changing the object’s interface. This can be useful when you want to be able to add behavior to an object at runtime.
class Decorator
def initialize(component)
@component = component
end
def operation
@component.operation
end
end
class ConcreteDecoratorA < Decorator
def operation
# decorator logic
@component.operation
end
end
class ConcreteDecoratorB < Decorator
def operation
# decorator logic
@component.operation
end
end
In this example, we have a Decorator
class and two subclasses that implement the operation
method. The operation
method can be called to execute the component’s operation and the decorator’s operation.
Ruby Factory Pattern
The factory pattern is used to create objects. This can be useful when you want to be able to create objects without knowing their exact type.
The factory pattern is often used with the command pattern to create commands.
class Factory
def self.create(type)
case type
when :animal
Animal.new
when :person
Person.new
else
raise ArgumentError, "Unknown type: #{type}"
end
end
end
factory = Factory.create(:animal) # => #
factory = Factory.create(:person) # => #
factory = Factory.create(:foo) # => ArgumentError: Unknown type: foo
In this example, we have a Factory
class that has a create
method. This method takes a type and returns an object of that type.
Ruby MVC Pattern
The MVC pattern is used to split an application into three parts: the model, the view, & the controller. This can be useful when you want to be able to change the look & feel of an application without changing the underlying code.
The MVC pattern is often used with the observer pattern to notify views when the model changes.
class Model
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
def remove_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update
end
end
end
class View
def update
# view logic
end
end
class Controller
def initialize
@model = Model.new
@view = View.new
@model.add_observer(@view)
end
def some_action
# controller logic
@model.notify_observers
end
end
In this example, we have a Model
class, a View
class, and a Controller
class.
The Model
class has methods for adding and removing observers. Whenever the notify_observers
method is called, it will call the update
method on all of the observers.
The View
class is an observer and the Controller
class is responsible for updating the model.
Ruby Adapter Pattern
The adapter pattern is used to adapt one interface to another. This can be useful when you want to be able to use an existing class with a new interface.
The adapter pattern is often used with the command pattern to adapt commands to a new interface.
class OldInterface
def some_method
# old interface logic
end
end
class NewInterface
def some_other_method
# new interface logic
end
end
class Adapter
def initialize(old_interface)
@old_interface = old_interface
end
def some_other_method
@old_interface.some_method
end
end
old_interface = OldInterface.new
adapter = Adapter.new(old_interface)
adapter.some_other_method
In this example, we have an OldInterface
class with a some_method
method and a NewInterface
class with a some_other_method
method.
We also have an Adapter
class that takes an old_interface
and adapts it to the new_interface
.
Ruby Iterator Pattern
The iterator pattern is used to iterate over a collection of items. This can be useful when you want to be able to iterate over a list of items without knowing the underlying implementation.
The iterator pattern is often used with the command pattern to iterate over a list of commands.
class Iterator
def initialize(collection)
@collection = collection
@index = 0
end
def has_next?
@index < @collection.length
end
def next
item = @collection[@index]
@index += 1
item
end
end
collection = [1, 2, 3]
iterator = Iterator.new(collection)
puts iterator.next # => 1
puts iterator.next # => 2
puts iterator.next # => 3
puts iterator.has_next? # => false
In this example, we have an Iterator
class that takes a collection
and iterates over it.
We can call the next
method to get the next item in the collection and the has_next?
method to check if there are any more items.
Ruby Proxy Pattern
The proxy pattern provides a surrogate or placeholder for another object. This can be useful when you want to be able to control access to an object.
The proxy pattern is often used with the command pattern to provide a surrogate for a command.
class Proxy
def initialize(subject)
@subject = subject
end
def some_method
@subject.some_method
end
end
class Subject
def some_method
# subject logic
end
end
subject = Subject.new
proxy = Proxy.new(subject)
proxy.some_method
In this example, we have a Proxy
class and a Subject
class. The Proxy
class has a some_method
method that delegates to the some_method
method of the Subject
class.
Ruby Wrapper Pattern
The wrapper pattern is used to wrap an object. This can be useful when you want to be able to add behavior to an object without changing the object’s interface.
The wrapper pattern is often used with the decorator pattern to add behavior to an object at runtime.
class Wrapper
def initialize(wrapped)
@wrapped = wrapped
end
def some_method
@wrapped.some_method
end
end
class Wrapped
def some_method
# wrapped logic
end
end
wrapped = Wrapped.new
wrapper = Wrapper.new(wrapped)
wrapper.some_method
In this example, we have a Wrapper
class and a Wrapped
class. The Wrapper
class has a some_method
method that delegates to the some_method
method of the Wrapped
class.
Conclusion
Each design pattern has its advantages and disadvantages. Some patterns are more suited for a specific type of application than others. You We highly recommend this resource to continue learning about design patterns.