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.