personVisitor
Visitor is a behavioral design pattern that allows you to add new behaviors without changing existing code.
We will create a book ordering system that will also allow us to add special discounts.
First, we will create our Book, which will have a title and a price.
class Book
attr_accessor :title, :price
def initialize(title:, price:)
@title = title
@price = price
end
end
The next step will be to create an order, which will allow us to add books to the order and display the order total.
class Order
def initialize
@books = []
end
def add_book(book)
@books << book
end
def total
@books.sum(&:price)
end
end
The first thing we need to do to implement the Visitor pattern is to create a Visitable module.
module Visitable
def accept(visitor)
visitor.visit(self)
end
end
Then we need to attach it to Book class.
class Book
include Visitable
attr_accessor :title, :price
def initialize(title:, price:)
@title = title
@price = price
end
end
In the Order class we also create an accept method that works on our list of books.
class Order
def initialize
@books = []
end
def add_book(book)
@books<< book
end
def total
@books.sum(&:price)
end
def accept(visitor)
@books.each do |book|
book.accept(visitor)
end
end
end
At this point we can create our base Visitor class that prepare a common interface for new visitors.
class Visitor
def visit(object)
raise NoMethodError
end
end
And now we are able to create our implementation of a specific that will be responsible for reducing the prices by half.
class DiscountVisitor < Visitor
def visit(object)
object.price /= 2
end
end
We can now use Visitor pattern in real code
# Create two books
hobbit = Book.new(title: 'The Hobbit', price: 32)
harry_potter = Book.new(title: "Harry Potter and the Philosopher's Stone", price: 28)
# Create an order
order = Order.new
# Add books to the order
order.add_book(hobbit)
order.add_book(harry_potter)
# Order returns a sum of books prices
order.total # => 60
# Attach discount to the book
order.accept(DiscountVisitor.new)
# Adding a discount cuts prices in half
order.total # => 30
The pattern prepared in this way allows us to add new code without interfering with the base code.
However in Ruby code, this pattern is rarely seen because many of the functions of this pattern can be replaced with a simple block.