Responsive Advertisement

JavaScript #17: Visitor design pattern in JavaScript

 


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. Series about design pattern.

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!

Đăng nhận xét

0 Nhận xét