Separate algorithms from the objects on which they operate
Swift — Problems Catalogue #22
Problem Definition:
Consider the following scenario. You have to implement an application that allows users to organize and play their music tracks.
Considering that could be multiple tracks and categories, and different operations on the tracks, we need a way to separate the management of tracks from the operations that can be performed on them in order to support an easy way to add new operations or make changes on them.
Problem Solution:
Solution —Visitor it’s a behavioral design pattern that separates an algorithm from an object structure on which it operates. The pattern defines a new type of object, called a visitor, that has methods for visiting each type of element in the object structure. The elements accept the visitor and use it to perform the operation.
It allows you to add new operations to the elements of an object structure without changing the classes of the elements. The elements have a common protocol and specific classes of Visitors. When a Visitor is passed to an element, it calls the corresponding visit
method on the visitor who performs the operation.
Real-World Usage:
First, we need to define aMusicTrack
protocol that has a single method, accept(visitor:)
, which will be used to accept a visitor.
Afterwards, we need to define the Song
and Podcast
classes which are concrete implementations of the MusicTrack
protocol. They represent different types of tracks that can be visited. The artist
and title
properties in the Song
class, and the title
and episode
properties in the Podcast
class, are used to store information about the track.
Next, we need to define the MusicTrackVisitor
protocol that has two methods, visit(track: Song)
and visit(track: Podcast)
, which are used to perform operations on tracks of the corresponding type.
Afterwards, we need to define a MusicLibraryVisitor
class which is a concrete implementation of the MusicTrackVisitor
protocol, it defines an operation that can be performed on tracks. It has two properties songs
and podcasts
which will be used to store the visited tracks, it adds the visited tracks to corresponding arrays.
The key idea of this pattern is that elements have a common protocol and specific classes of Visitors. When a Visitor is passed to an element, it calls the corresponding visit
method on the visitor which performs the operation.
Finally, let’s bring everything together by creating some songs, some podcasts and a visitor object.
When a song or a podcast is passed to the accept(visitor:)
method, it calls the visit(track: Song)
or visit(track: Podcast)
method on the visitor, which in turn will add the track to the corresponding array.
This way each track will call the corresponding visit method on the visitor, adding the track to the corresponding array, and then we print the songs and podcasts array.
The Visitor pattern allows you to add new operations to the elements of an object structure without changing the classes of the elements. The pattern defines a new type of object, the visitor, that has methods for visiting each type of element in the object structure. The elements accept the visitor and use it to perform the operation.
Without using the Visitor pattern, the operations would need to be included in the Song
and Podcast
classes, which would make it difficult to add new operations or make changes to existing ones without affecting the classes themselves.
From this point on, the sky is the limit 🚀 well…almost.
Of course, this design pattern has its limitations but used in moderation, it’s a great tool in our development toolbox.
This is the next article in the Swift Problems Catalogue series in which I’ll tackle general software development problems. The aim is to have a quick reference guide that can be easily accessed when having a design/algorithm dillemma.
Let me know what you think and don’t be shy to share where and when this pattern simplified your coding experience 🎶