I. Visitor Pattern

The Visitor pattern allows you to define new operations on the elements of an object structure without changing the classes of the elements themselves. This pattern is useful when you have a complex object structure with multiple types of elements and want to perform different operations on them without modifying the classes of the elements.

In Ruby, the Visitor pattern can be implemented using a visitor class that defines the operations to be performed on the elements of the object structure. Here’s an example of the Visitor pattern in Ruby:

class Element
  def accept(visitor)
    raise NotImplementedError, "#{self.class} does not implement accept(visitor)"
  end
end

class ConcreteElementA < Element
  def accept(visitor)
    visitor.visit_concrete_element_a(self)
  end
end

class ConcreteElementB < Element
  def accept(visitor)
    visitor.visit_concrete_element_b(self)
  end
end

class Visitor
  def visit_concrete_element_a(element)
    raise NotImplementedError, "#{self.class} does not implement visit_concrete_element_a(element)"
  end

  def visit_concrete_element_b(element)
    raise NotImplementedError, "#{self.class} does not implement visit_concrete_element_b(element)"
  end
end

class ConcreteVisitor < Visitor
  def visit_concrete_element_a(element)
    puts "Visited ConcreteElementA"
  end

  def visit_concrete_element_b(element)
    puts "Visited ConcreteElementB"
  end
end

# Usage

elements = [ConcreteElementA.new, ConcreteElementB.new]
visitor = ConcreteVisitor.new

elements.each { |element| element.accept(visitor) }

II. Memento Pattern

The Memento pattern allows you to capture and externalize an object’s internal state so that the object can be restored to that state later. This pattern is useful when you want to save and restore the state of an object without exposing its internal structure.

In Ruby, the Memento pattern can be implemented using a memento class that stores

class Memento
  attr_reader :state

  def initialize(state)
    @state = state
  end
end

class Originator
  attr_accessor :state

  def create_memento
    Memento.new(state)
  end

  def restore_memento(memento)
    @state = memento.state
  end
end

class Caretaker
  attr_accessor :memento

  def add_memento(memento)
    @memento = memento
  end
end

# Usage

originator = Originator.new
caretaker = Caretaker.new

originator.state = "State1"
caretaker.add_memento(originator.create_memento)

originator.state = "State2"
caretaker.add_memento(originator.create_memento)

originator.state = "State3"
caretaker.add_memento(originator.create_memento)

originator.restore_memento(caretaker.memento)
puts originator.state

III. Mediator Pattern

The Mediator pattern defines an object that encapsulates how a set of objects interact with each other. This pattern is useful when you have a complex system with multiple objects that need to communicate with each other, and you want to decouple the objects from each other to make the system more maintainable and flexible.

In Ruby, the Mediator pattern can be implemented using a mediator class that defines the communication between the objects. Here’s an example of the Mediator pattern in Ruby:

class Mediator
  def initialize
    @colleagues = []
  end

  def add_colleague(colleague)
    @colleagues << colleague
  end

  def send(message, colleague)
    @colleagues.each do |c|
      c.receive(message) unless c == colleague
    end
  end
end

class Colleague
  attr_reader :mediator

  def initialize(mediator)
    @mediator = mediator
    @mediator.add_colleague(self)
  end

  def send(message)
    @mediator.send(message, self)
  end

  def receive(message)
    puts "Received message: #{message}"
  end
end

# Usage

mediator = Mediator.new

colleague1 = Colleague.new(mediator)
colleague2 = Colleague.new(mediator)

colleague1.send("Hello from colleague1")
colleague2.send("Hello from colleague2")

IV. Flyweight Pattern

The Flyweight pattern is a structural pattern that allows you to share objects to reduce memory usage and improve performance. This pattern is useful when you have a large number of similar objects that can be shared to save memory and reduce the cost of creating new objects.

In Ruby, the Flyweight pattern can be implemented using a factory class that creates and manages flyweight objects. Here’s an example of the Flyweight pattern in Ruby:

class FlyweightFactory
  def initialize
    @flyweights = {}
  end

  def get_flyweight(key)
    @flyweights[key] ||= ConcreteFlyweight.new
  end
end

class Flyweight
  def operation
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

class ConcreteFlyweight < Flyweight
  def operation
    puts "ConcreteFlyweight operation"
  end
end

# Usage

factory = FlyweightFactory.new

flyweight1 = factory.get_flyweight("key1")
flyweight2 = factory.get_flyweight("key2")

flyweight1.operation
flyweight2.operation

V. Builder Pattern

The Builder pattern is a creational pattern that separates the construction of a complex object from its representation. This pattern is useful when you want to create an object step by step and have different representations of the object without exposing its internal structure.

In Ruby, the Builder pattern can be implemented using a builder class that constructs the object step by step. Here’s an example of the Builder pattern in Ruby:

class Product
  attr_accessor :part1, :part2, :part3

  def initialize
    @part1 = nil
    @part2 = nil
    @part3 = nil
  end
end


class Builder
  def build_part1
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  def build_part2
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  def build_part3
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end


class ConcreteBuilder < Builder
  attr_reader :product

  def initialize
    @product = Product.new
  end

  def build_part1
    @product.part1 = "Part1"
  end

  def build_part2
    @product.part2 = "Part2"
  end

  def build_part3
    @product.part3 = "Part3"
  end
end

class Director
  attr_reader :builder

  def initialize(builder)
    @builder = builder
  end

  def construct
    @builder.build_part1
    @builder.build_part2
    @builder.build_part3
  end
end

# Usage

builder = ConcreteBuilder.new
director = Director.new(builder)

director.construct

product = builder.product
puts product.part1
puts product.part2
puts product.part3