Welcome back to series about design pattern in JavaScript. In this article, we discuss about Visitor desgin pattern.
If you like
topic design pattern in JavaScript, this is a link for you follow.
Table of
contents:
- What is Visitor design pattern?
- How to implement Visitor design pattern?
- Practical application
What is
Visitor design pattern?
Visitor
design pattern is a behavioral design pattern that lets you separate algorithms
from the objects on which they operate.
A pratical
result of this separation is the ability to add new operations to existing
object structures without modifying the structures. It is one way to follow the
open/closed principle.
What
problems can the Visitor design pattern solve?
It should be
possible to define a new operation for (some) classes of an object structure
without changing the classes.
What
solution does the Visitor design pattern describe?
Define a
separate (visitor) object that implements an operation to be performed on
elements of an object structure.
Clients
traverse the object structure and call a dispatch operation accept (visitor) on
an element – that “dispatches” (delegates) the request to the “accept visitor
object”. The visitor object then performs the operation on the element (“visit
the element”).
How to implement Visitor design pattern?
|
Visitor design pattern class diagram |
The Visitor
interface declares a set of visiting methods that can take concreate elements
of an object structure as arguments. These methods may have the same names if
the program is written in a language that supports overloading, but the type of
their parameters must be different.
Each
Concrete Visitor implements several versions of the same behaviors, tailored
for different concrete element classes.
The Element
interface declares a method for “accepting” vistors. This method should have
one parameter declared with the type of the visitor interface.
Each
Concreate Element must implement the acceptance method. The purpose of this
method is to redirect the call to the proper visitor’s method corresponding to
the current element class. Be aware that even if a base element class
implements this method, all subclasses must still override this method in their
own classes and call the appropriate method on the visitor object.
The Client
usually represents a collection or some other complex object. Usually, clients
aren’t aware of all concrete element classes because they work with objects
from that collection via some abstract interface.
Practical
application
Let's go
through a real problem to better understand the Visitor design pattern. You
have a fashion store; you classify by groups and have separate prices for each
product to sell to customers. Let's create a Product object for your fashion
shop.
function Product(type, price) {
this.type = type
this.price = price
}
Quite simply,
products are grouped and have separate prices for each product. Let's add some
functionality to the Product object like get the type, get the price and set
the price for each product.
Product.prototype = {
getType: function () {
return this.type
},
getPrice: function () {
return this.price
},
setPrice: function (p) {
return this.price = p
}
}
Black Friday
is coming, your shop will discount items by category as follows. If the product
has a shirt type, then 10% discount, skirt 20% discount, the rest 5% discount.
Let's create a list of products and fulfill the above request.
const data = [
blueShirt = new Product('shirt', 50),
redSkirt = new Product('skirt', 40),
blackShoes = new Product('shoes', 100),
]
data.forEach(item => {
console.log(item.getPrice())
if (item.getType() === 'shirt') {
item.setPrice(item.getPrice() - (item.getPrice() * 0.1))
}
else if (item.getType() === 'skirt') {
item.setPrice(item.getPrice() - (item.getPrice() * 0.2))
}
else {
item.setPrice(item.getPrice() - (item.getPrice() * 0.05))
}
console.log(item.getPrice())
})
// => 50 - 45
// => 40 - 32
// => 100 - 95
Great, the
problem was solved quite easily. And until Black Friday is over and items are
back at their original prices, it's your job to erase the discount algorithms. What
if discounting for multiple products becomes complicated or you apply multiple
discounts at the same time and you have to stop it. This seriously violates
Open/Closed Priciple in OOP programming.
Now we will
solve this problem through Visitor design pattern. We keep the same Product
object and add an accept() method. This method will accept functions and allow
it to affect objects.
Product.prototype = {
getType: function () {
return this.type
},
getPrice: function () {
return this.price
},
setPrice: function (p) {
return this.price = p
},
accept: function (visitorFunc) {
visitorFunc(this)
}
}
Looking at
the accept() method we see that the accept() method takes a callback function
visitorFunc() and the function vistorFunc() will receive the object itself.
This will be analyzed in more detail below.
function discount(pro) {
// Type ==== shirt: discount 10%
if (pro.getType() === 'shirt') {
pro.setPrice(pro.getPrice() - (pro.getPrice() * 0.1))
}
// Type ==== skirt: discount 20%
else if (pro.getType() === 'skirt') {
pro.setPrice(pro.getPrice() - (pro.getPrice() * 0.2))
}
// Rest: discount 5%
else {
pro.setPrice(pro.getPrice() - (pro.getPrice() * 0.05))
}
}
We create a
discount function that accepts a Product object. Coinciding with the callback
of the accept method above, we can see that the adjustment will be based on
which function the Product object allows to edit and the function can only edit
the allowed objects.
const data = [
blueShirt = new Product('shirt', 50),
redSkirt = new Product('skirt', 40),
blackShoes = new Product('shoes', 100),
]
data.forEach((item) => {
console.log(item.getPrice())
item.accept(discount)
console.log(item.getPrice())
})
Along with
the previous data, we use the forEach() method to accept the price modification
objects.
// => 50 - 45
// => 40 - 32
// => 100 - 95
The result
is still as we want. More advanced, in the meantime we want to apply another
promotion along with the original promotion.
function anotherDiscount(pro) {
// Type === shoes: discount 30%
if (pro.getType() === 'shoes') {
pro.setPrice(pro.getPrice() - (pro.getPrice() * 0.3))
}
// Rest: no discounting
}
Let's see
what the result will be?
data.forEach((item) => {
console.log(item.getPrice())
item.accept(discount)
console.log(item.getPrice())
item.accept(anotherDiscount)
console.log(item.getPrice())
})
// => 50 - 45 - 45
// => 40 - 32 - 32
// => 100 - 95 - 66.5
You may find
it simpler to discount products. You can also apply many different types of discounts,
and you can not accept if the promotion is over.
Let's go
through the analysis of Visitor design pattern.
Pros:
Open/Closed
Principle. You can introduce a new behavior that can work with objects of diffent
classes without changing these classes.
Single Responsibility
Principle. You can move multiple versions of same behavior into the same class.
A visitor
object can accumulate some useful information while working with various
objects. This might be handy when you want to traverse some complex object
structure, such as an object tree, and apply the vistor to each object of this
structure.
Cons:
You need to
update all visitors each time a class gets added to or removed from the element
hierarchy.
Visitors
might lack the necessary access to the private fields and methods of the
elements that they’re supposed to work with.
Conclusion:
If your
program is simple, you don't need to use the Visitor design pattern. If your
system is more complex, requires more algorithms you can consider using Visitor
design pattern.
If you have any ideas feel free to comment below. Thank you for joining with me. Have a good day!
0 Nhận xét