I. Prototype Pattern
The Prototype pattern allows you to create new objects by copying an existing object, known as the prototype. This pattern is useful when creating new objects is resource-intensive or complex, and you want to avoid the overhead of creating new instances from scratch. In Ruby, the Prototype pattern can be implemented using the clone
method to create copies of existing objects.
Here’s an example of the Prototype pattern in Ruby:
class Prototype
def clone
raise NotImplementedError, "#{self.class} does not implement clone"
end
end
class ConcretePrototype1 < Prototype
def initialize
@attribute = 'Prototype 1'
end
def clone
ConcretePrototype1.new(@attribute)
end
end
class ConcretePrototype2 < Prototype
def initialize
@attribute = 'Prototype 2'
end
def clone
ConcretePrototype2.new("Cloned #{@attribute}")
end
end
# Usage
prototype1 = ConcretePrototype1.new
clone1 = prototype1.clone
puts clone1.inspect
prototype2 = ConcretePrototype2.new
clone2 = prototype2.clone
puts clone2.inspect
In this example, we have two concrete prototype classes, ConcretePrototype1
and ConcretePrototype2
, that implement the clone
method to create copies of themselves. The clone
method returns a new instance of the concrete prototype class with the same attributes as the original object.
II. Abstract Factory Pattern
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is useful when you need to create multiple objects that are part of a common theme or have dependencies between them. In Ruby, the Abstract Factory pattern can be implemented using factory classes that create related objects.
Here’s an example of the Abstract Factory pattern in Ruby:
class AbstractFactory
def create_product_a
raise NotImplementedError, "#{self.class} does not implement create_product_a"
end
def create_product_b
raise NotImplementedError, "#{self.class} does not implement create_product_b"
end
end
class ConcreteFactory1 < AbstractFactory
def create_product_a
ConcreteProductA1.new
end
def create_product_b
ConcreteProductB1.new
end
end
class ConcreteFactory2 < AbstractFactory
def create_product_a
ConcreteProductA2.new
end
def create_product_b
ConcreteProductB2.new
end
end
class AbstractProductA
def operation_a
raise NotImplementedError, "#{self.class} does not implement operation_a"
end
end
class ConcreteProductA1 < AbstractProductA
def operation_a
puts "ConcreteProductA1 operation"
end
end
class ConcreteProductA2 < AbstractProductA
def operation_a
puts "ConcreteProductA2 operation"
end
end
class AbstractProductB
def operation_b
raise NotImplementedError, "#{self.class} does not implement operation_b"
end
end
class ConcreteProductB1 < AbstractProductB
def operation_b
puts "ConcreteProductB1 operation"
end
end
class ConcreteProductB2 < AbstractProductB
def operation_b
puts "ConcreteProductB2 operation"
end
end
# Usage
factory1 = ConcreteFactory1.new
product_a1 = factory1.create_product_a
factory2 = ConcreteFactory2.new
product_a2 = factory2.create_product_a
product_a1.operation_a
product_a2.operation_a
In this example, we have two concrete factory classes, ConcreteFactory1
and ConcreteFactory2
, that create families of related products, ConcreteProductA1
and ConcreteProductB1
, and ConcreteProductA2
and ConcreteProductB2
, respectively. The abstract factory class AbstractFactory
defines the interface for creating product families, and the concrete factory classes implement this interface to create specific products.
III. Bridge Pattern
The Bridge pattern decouples an abstraction from its implementation, allowing them to vary independently. This pattern is useful when you want to separate the interface of an object from its implementation and provide a way to change the implementation dynamically at runtime. In Ruby, the Bridge pattern can be implemented using composition to separate the abstraction and implementation.
Here’s an example of the Bridge pattern in Ruby:
class Abstraction
attr_writer :implementation
def operation
@implementation.operation_implementation
end
end
class Implementation
def operation_implementation
raise NotImplementedError, "#{self.class} does not implement operation_implementation"
end
end
class ConcreteImplementationA < Implementation
def operation_implementation
puts "ConcreteImplementationA operation"
end
end
class ConcreteImplementationB < Implementation
def operation_implementation
puts "ConcreteImplementationB operation"
end
end
# Usage
abstraction = Abstraction.new
implementation_a = ConcreteImplementationA.new
implementation_b = ConcreteImplementationB.new
abstraction.implementation = implementation_a
abstraction.operation
abstraction.implementation = implementation_b
abstraction.operation
In this example, we have an abstraction class Abstraction
that delegates the implementation of its operation to an implementation class. The implementation class defines the concrete behavior of the operation, and different implementations can be provided to the abstraction at runtime. This allows the abstraction and implementation to vary independently and be changed dynamically.
IV. Facade Pattern
The Facade pattern provides a unified interface to a set of interfaces in a subsystem, making it easier to use and reducing the complexity of the system. This pattern is useful when you want to provide a simple interface to a complex subsystem or hide the implementation details of a subsystem from clients. In Ruby, the Facade pattern can be implemented using a facade class that delegates calls to the subsystem’s components.
Here’s an example of the Facade pattern in Ruby:
class SubsystemA
def operation_a
puts "SubsystemA operation"
end
end
class SubsystemB
def operation_b
puts "SubsystemB operation"
end
end
class Facade
def initialize
@subsystem_a = SubsystemA.new
@subsystem_b = SubsystemB.new
end
def operation
@subsystem_a.operation_a
@subsystem_b.operation_b
end
end
# Usage
facade = Facade.new
facade.operation
In this example, we have two subsystem classes, SubsystemA
and SubsystemB
, that provide specific operations. The facade class Facade
encapsulates the subsystems and provides a simple interface to their operations. Clients can interact with the facade to perform complex operations without needing to know the details of the subsystems.
V. Conclusion
In this article, we explored more design patterns in Ruby, including the Prototype, Abstract Factory, Bridge, and Facade patterns. These patterns provide elegant solutions to common design problems and help create maintainable, flexible code. By understanding and applying design patterns in your Ruby projects, you can improve code quality, readability, and maintainability.
Public comments are closed, but I love hearing from readers. Feel free to contact me with your thoughts.