- Published on
Visitor design pattern and the application
- Pramod Kumar
Design patterns are just a way of designing the process flow of the application so that it is easier for humans to understand, develop or enhance, and scale it. Factory pattern is something that I use very often. The visitor design pattern is not at all a regular design pattern I use. I used it for the first time and understood the complexity immediately.
I am not an academic person for sure. By academic, I mean, I don’t read stuff upfront and use it in appropriate applications. I believe in “do whatever works for the situation”.
I had spent long enough time in the lending business and made enough mistakes already in the process of making a pseudo Loan Management System. Eventually, it was time we had to revamp it and spend time and effort building an actual LMS that powers our lending business.
It was not easy for sure. My CTO, Rahul, was the key person in driving me in the correct direction. We used to spend hours on whiteboard discussing how an actual LMS (Loan Management System) work, what components it contains, what components we build and the implementation etc. From day one we were clear that financial accounting (book-keeping) is the spine of the entire setup.
To just give you the context, any lending business deals with loans, fund transfers, repayments, different types of charges, dues, bills, etc. On the other hand, we needed to derive the state of a loan at any point in time. The state contains details like, what is the outstanding balance, charges that are due, available limit, etc. I don’t want to go deep and explain these terms and they are irrelevant to the topic. Just stick with me :)
Going from first principles, if a loan has all these different types of entities happening on it, just store them in collections like fund transfers, repayments, charges, etc. That makes sense to start with. Then we quickly identified that, if you look closer, these are different events happening in a timeline. One can just travel through the events in order and calculate the state accordingly.
As explained in the above picture, a state is a function of a list of events till any given point in time. This is so powerful that we can easily derive the state of a loan at any given point in time!
We were excited! On the other hand, we also cracked the financial accounting problem by building the double entry system — (pyluca)[https://github.com/datasignstech/pyluca]; which we eventually open-sourced.
That means we needed to store all above mentioned (fund transfers, repayments, charges, bills, etc.) entities in the same place with the date attribute in it. These are nothing but events. Every event has a type and a date of occurrence.
The Visitor Design Pattern
We needed a function that takes the list of events and throws out a state of a loan. There is a particular action to be performed for each of these events. We can certainly put the action logic inside the event. But should we do that? This is where the visitor pattern comes into the picture.
According to Wikipedia
In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates.
That means the event has nothing to do with the action itself. The action is a side effect of the event according to the visitor. As I mentioned earlier, financial book-keeping is the backbone. We created a visitor, which goes through all the events, one by one and adds the ledger entries accordingly. Similarly, there can be another visitor, which is responsible for calculating the bills and charges. To derive the state of the loan, just take the result of all the visitors and apply the state calculation algorithm. It is so clean now!
You can read more about the pattern itself here — https://en.wikipedia.org/wiki/Visitor_pattern Remember, patterns are not about the implementation. It is the generic, high-level design of the layers and the logic flow.
The following pseudo-code depicts the pattern precisely. Any event should implement a function visit which takes the visitor as an input. As python does not support method overloading, I have created multiple visit functions inside the visitor. It could have been method overloading if it is a Java kind of programming language.
There are two kinds of events, EventRepayment and EventDrawdown. On the other hand, there are two visitors, AccountingVisitor and CreditScoreVisitor each doing its job for each event. For example, AccountingVisitor just contains the logic of passing the ledger entries at each event. Same for CreditScoreVisitor it contains logic to score the customer’s creditworthiness even though the shown logic is just dumb ;)
The function which generates the state now just iterates through the list of events and visits it for each visitor. Take the result of both of the visitors and generate the net loan state. This separates the logic for creating the ledger entries and calculating the credit score. This is awesome, isn’t it?
class Visitor: @abstractmethod def visit_repayment(event): pass @abstractmethod def visit_drawdown(event): pass class Event: def __init__(self, date: datetime): self.date = date @abstractmethod def visit(visitor: Visitor): pass class EventRepayment(Event): def __init__(self, amount: float, date: datetime): super(EventRepayment, self).__init__(date) self.amount = amount def visit(visitor: Visitor): visitor.visit_repayment(self) class EventDrawdown(Event): def __init__(self, amount: float, date: datetime): super(EventRepayment, self).__init__(date) self.amount = amount def visit(visitor: Visitor): visitor.visit_drawdown(self) class AccountingVisitor(Visitor): self __init__(self): self.ledger_entries =  def visit_repayment(event: EventRepayment): # append the ledger entries for repayment print(event.amount, event.date) def visit_drawdown(event: EventDrawdown): # append the ledger entries for drawdown print(event.amount, event.date) class CreditScoreVisitor(Visitor): def __init__(self): self.score = 0 def visit_repayment(event: EventRepayment): self.score += 1 def visit_drawdown(event: EventDrawdown): self.score -= 1 def get_loan_state(events: List[Event]): visitors = [ AccountingVisitor(), CreditScoreVisitor() ] for event in events: for visitor in visitors: event.visit(visitor) return compute_state_from_visitors(visitors)
Imagine there is a school with multiple classrooms, dining halls, bathrooms, etc. Let’s say there is an inspection team visited the school to check the quality of the facilities. Instead of the entire team visiting each class and checking if the board, roof, tiles, fans, etc. are functioning randomly, it is better to assign each person to check the board, roof, tiles, etc. That way, the work is clearly segregated and the inspection could be better. All the visitors can inspect at their own pace and submit their own report to the inspection head who is sitting at head office. Once the inspection head receives all reports from all the inspectors, he can then publish the final report.
This has actually enabled us to come up with different visitors for different purposes, for example, the monthly provisions (again an accounting jargon; read more about it here — https://en.wikipedia.org/wiki/Provision_(accounting)) we do.
This pattern is not used so often because it messes up and confuses the developers if implemented poorly. You can use it when you have a use case like few consumers can consume different types of entities to ultimately give out the desired output.
We did not do anything keeping the Visitor design pattern in mind, it just so happened that this pattern solves our problem. Don’t try to enforce any particular pattern for your problems. Just pick what suits your problem.
Hope you had a great time reading it. Follow me on (Twitter)[https://twitter.com/pramodk73], I keep posting about my work on learning in general. Cheers :)