Design Patterns Uncovered: The Visitor Pattern

Today we're going to take a look at the Visitor pattern. Of all of the patterns that I've used so far, Visitor is by far the most powerful and convenient.  

Vistors in the Real World 

A real world analogy always helps with the understanding of a design pattern. One example I have seen for the Visitor pattern in action is a taxi example, where the customer calls orders a taxi, which arrives at his door. Once the person sits in, the visiting taxi is in control of the transport for that person. 

Shopping in the supermarket is another common example, where the shopping cart is your set of elements. When you get to the checkout, the cashier acts as a visitor, taking the disparate set of elements (your shopping), some with prices and others that need to be weighed, in order to provide you with a total. 

It's a difficult pattern to explain in the real world, but things should become clearer as we go through the pattern definition, and take a look at how to use it in code.  


The Visitor Pattern

The Visitor is known as a  behavioural  pattern, as it's used to manage algorithms, relationships and responsibilities between objects. The definition of Visitor provided in the original Gang of Four book on Design Patterns states: 

Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure. 

What the Visitor pattern actually does is create an external class that uses data in the other classes. If you need to perform operations across a dispate set of objects, Visitor might be the pattern for you. The GoF book says that the Visitor pattern can provide additional functionality to a class without changing it. Let's see how that can work, first by taking a look at the classic diagram definition of  the Visitor pattern:

The core of this pattern is the Visitor interface. This interface defines a visit operation for each type of ConcreteElement in the object structure. Meanwhile, the ConcreteVisitor implements the operations defined in the Visitor interface. The concrete visitor will store local state, typically as it traverses the set of elements. The element interface simply defines an accept method to allow the visitor to run some action over that element - the ConcreteElement will implement this accept method. 

Where Would I Use This Pattern?

The pattern should be used when you have distinct and unrelated operations to perform across a structure of objects. This avoids adding in code throughout your object structure that is better kept seperate, so it encourages cleaner code. You may want to run operations against a set of objects with different interfaces.  Visitors are also valuable if you have to perform a number of unrelated operations across the classes.

In summary, if you want to decouple some logical code from the elements that you're using as input, visitor is probably the best pattern for the job.

So How Does It Work In Java?

The following example shows a simple implementation of the pattern in Java. The example we'll use here is a postage system. Our set of elements will be the items in our shopping cart. Postage will be determined using the type and the weight of each item, and of course depending on where the item is being shipped to. 

Let's create a seperate visitor for each postal region. This way, we can seperate the logic of calculating the total postage cost from the items themselves. This means that our individual elements don't need to know anything about the postal cost policy, and therefore, are nicely decoupled from that logic. 

First, let's create our general visitable  interface: 

1. //Element interface
2. public interface Visitable
3. {
4. public void accept(Visitor visitor);
5. }

Now, we'll create a concrete implementation of our interface, a Book.

01. //concrete element
02. public class Book implements Visitable
03. {
04. private double price;
05. private double weight;
06.  
07. //accept the visitor
08. public void accept(Visitor vistor)
09. {
10. visitor.visit(this);
11. }
12.  
13. public double getPrice()
14. {
15. return price;
16. }
17.  
18. public double getWeight()
19. {
20. return weight;
21. }
22. }

As you can see it's just a simple POJO, with the extra accept method added to allow the visitor access to the element. We could add in other types here to handle other items such as CDs, DVDs or games. 


Now we'll move on to the Visitor interface. For each different type of concrete element here, we'll need to add a visit method. As we'll just deal with Book for now, this is as simple as: 

1. public interface Visitor
2. {
3. public void visit(Book book);
4. //visit other concrete items
5. public void visit(CD cd);
6. public void visit(DVD dvd);  
7. }

The implementation of the Vistor can then deal with the specifics of what to do when we visit a book. 

01. public class PostageVisitor implements Visitor
02. {
03. private double totalPostageForCart;    
04. //collect data about the book
05. public void visit(Book book) 
06. {
07. //assume we have a calculation here related to weight and price
08. //free postage for a book over 10     
09. if(book.getPrice() < 10.0)
10. {
11. totalPostageForCart += book.getWeight() * 2;  
12. }
13. }
14.  
15. //add other visitors here
16. public void visit(CD cd){...}
17. public void visit(DVD dvd){...}
18. //return the internal state
19. public double getTotalPostage()
20. {
21. return totalPostageForCart;   
22. }
23. }

As you can see it's a simple formula, but the point is that all the calculation for book postage is done in one central place. 

To drive this visitor, we'll need a way of iterating through our shopping cart, as follows: 

01. public class ShoppingCart
02. {
03. //normal shopping cart stuff
04. private ArrayList<Visitable> items;
05.  
06. public double calculatePostage()
07. {
08. //create a visitor
09. PostageVisitor visitor = new PostageVisitor();
10. //iterate through all items
11. for(Visitable item: items)
12. {
13. item.accept(visitor);
14. }
15. double postage = visitor.getTotalPostage();
16. return postage;
17.  
18. }
19.  
20.  
21. }

Note that if we had other types of item here, once the visitor implements a method to visit that item, we could easily calculate the total postage.

So, while the Visitor may seem a bit strange at first, you can see how much it helps to clean up your code. That's the whole point of this pattern - to allow you seperate out certain logic from the elements themselves, keeping your data classes simple.

Watch Out for the Downsides

The arguments and return types for the visiting methods needs to be known in advance, so the Visitor pattern is not good for situtations where these visited classes are subject to change. Every time a new type of Element is added, every Visitor derived class must be amended. 

Also, it can be difficult to refactor the Visitor pattern into code that wasn't already designed with the pattern in mind. And, when you do add your Visitor code, it can look obscure. The Visitor is powerful, but you should make sure to use it only when necessary.   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
随着我国近年来高校不断的进行扩招,2022年全国高校的毕业生人数已经超过一千万人,而在这个时代的大学生早已不像上世纪八九十年代一样,毕业就可以分配工作,所以在当今这个时代毕业生找工作是个非常困难的事情。再加上近几年受到国内疫情的影响,很多企业都在进行缩编,招聘新员工的数量较往年相比有很大的减少,这给大学生找工作带来了更大的挑战。在计算机不够发达的年代,大学生们找工作都是先打印好简历,然后去多家公司进行面试,整个面试的流程和对企业信息的获取都是非常麻烦的,在本就时间不够充足的情况下,这种招聘的方式效率非常低。 但随着计算机技术和网络技术的不断发展,我国各个领域的信息管理的方式早已发生了改变。以往企业方和大学生对招聘信息的管理都是通过手工的方式在纸张上进行记录和管理的,但这种方式非常的不方便,而且增加出错的概率。随着我国经济的高速发展以及信息技术的不断进步,通过人工对招聘信息进行管理的方式早已被淘汰。本人通过对市场的调研和详细需求分析并结合了大学四年学习的知识开发了一款基于SSM的校园招聘信息管理系统。本系统的后台开发技术为JSP,前端语言为HTML,数据库选用的是MYSQL数据库,本系统分为学生用户、企业方用户和系统管理员三个角色,本系统的开发可以为所有的大学生和企业方管理人员提供专业的招聘信息管理服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值