Reputation: 14736
The visitor design pattern is a way of separating an algorithm from an object structure it operates on. That is official definition of it. I am trying to figure out how this doesn't break encapsulation. If say for example I have different types of classes for different types of bank accounts [Saving/Fixed/Current] implementing abstract class Account, should I put the calculate interest method as an abstract method in the abstract Account class or do I send the account type to the Visitor implementation and calculate it there?
Method 1: Should the Visitor implementation be responsible for calculating interest for the different account types?
public interface IInterestVisitor
{
void GetInterest(Savings AccountType);
void GetInterest(Fixed AccountType);
void GetInterest(Current AccountType);
}
Method 2: Or should the Account class's implementers do it?
public abstract class Account
{
public abstract void AcceptCalculateInterestVisitor(IInterestVisitor iv);
public abstract int CalculateInterestAmount();
}
If I use method 1, which is the visitor implementation that implements IInterestVisitor as above, then the job of calculating interest will be delegated to the visitor class. Using this approach, if I add another account type, then I will need to modify the visitor implementation everytime a new account comes along.
However, if I leave the interest calculation bit to the abstract Account classes implementations as above in method 2, then in my opinion [correct me if I am wrong here] I am not breaking encapsulation. In addition there is less code to modify as all I do is add a new class and have the visitor implement an interface like the one below
public interface IInterestVisitor
{
void GetInterest(Account AccountType);
}
public class InterestVisitor : IInterestVisitor
{
public void GetInterest(Account AccountType)
{
int i = AccountType.CalculateInterestAmount();
Console.WriteLine(i);
}
}
As you can see, there is no modification needed for the interest visitor class using method 2. Does method 1 break encapsulation? Can method 2 still be called a visitor pattern?
Thanks for reading...
Upvotes: 8
Views: 2125
Reputation:
Here is example of visitor which report the status in many ways without impacting the original Account classes/objects. It is not good to replace capabilities of Visitors in virtual functions like PrintReport(), SmsReport() etc where you will have too many methods to implement. Using Visitor, you could extend the capabilities in other way without affecting the object. I gave this example, since you have asked/mentioned where a visitor would fits.. In my opinion, visitor would not fit both the methods you have mentioned.
public interface IAccount
{
//Below are properties and methods which you expose outside
// accoring to your class/domain
double Interest { get;}
//double Balance { get;}
//ContactInfo Contact{ get;}
void Visit(IAccountVisitor visitor);
}
public class AccountBase
{
}
public class FixedDeposit: AccountBase, IAccount
{
double Interest {
get{
return CalculateInterest(); //don't change object state
}
;}
protected double CalculateInterest()
{
return <<your expression to calculate>>;
}
public void Visit(IAccountVisitor visitor)
{
visitor.Visit(this);
}
}
public class XYZBlashBlahDeposit: AccountBase, IAccount
{
public void Visit(IAccountVisitor visitor)
{
visitor.Visit(this);
}
}
...
...
public interface IAccountVisitor
{
//void Report(IAccount account); //This is prefered and safe and
void Visit(Account account);
}
public class PrintReportVisitor: IAccountVisitor
{
public void Visit(Account account)
{
//Get object information
//Print to printer
}
}
public class DisplayReportVisitor: IAccountVisitor
{
public void Visit(Account account)
{
//Get object information
//Display on monitor
}
}
public class ReportAudioVisitor: IAccountVisitor
{
public void Visit(Account account)
{
//Get object information
//Read out the information
}
}
public class SmsReportVisitor: IAccountVisitor
{
public void Visit(Account account)
{
//Get object information
//Send SMS to my mobile registered with account
}
}
public class EmailReportVisitor: IAccountVisitor
{
public void Visit(Account account)
{
//Get object information
//Send Email to my email registered with account
}
}
See if you have application, how to use Visitor.
public class ReportViewer: UserControl
{
IAccount _Account;
public ReportViewer(IAccount account)
{
this._Account = account;
InitializeComponent();
}
void btnClick_Print()
{
_Account.Visit(new PrintReportVisitor());
}
void btnClick_ViewReport()
{
_Account.Visit(new DisplayReportVisitor(this));
}
void btnClick_SendSMS()
{
_Account.Visit(new SMSReportVisitor(this));
}
void btnClick_SendEmail()
{
_Account.Visit(new EmailReportVisitor(this));
}
}
Upvotes: 1
Reputation: 6908
@Tigran pointed out that Visitor is useful when you can't change the classes being visited.
Since you are using C#, it is probably more modular and effective to use Extension Methods. They do the same thing, adding functionality to a type after the fact, but you no longer need to carry around all of the possible implementations in one place.
Upvotes: 1
Reputation: 55897
Visitor helps in situations where the logic may depend upon both type of the type of the Visted as well as the type of Visitor - it's a way of structuring double dispatch.
In your scenario you have a requirement to calculate interest and this is a reasonable operation of an account and depends very clearly on the nature of the account. Hence as you point out Visitor adds no value, and seems inappropriate.
Now lets take a slightly more complex set of calculations. Imagine that you have a variety of savings accounts some are regular savings accounts, some are conditional on stock market behaviours, some have elements of life-insurance. For such accounts we have methods to obtains data such interest rates, bonus rates, regular payment schedules, maturity dates and so on, each different for different types of accounts.
We also need to perform certain calculations: what's predicted value of the account on a certain date? What's the account's value as loan collatoral now? What's the value of the account if closed today. Such calculations will need to be done differently (perhaps using the raw data getters) for each account type.
Now we see some value in a Visitor pattern. As we devise a new interesting calculation we write a new Visitor, we don't need to release new versions of the account classes.
Upvotes: 1
Reputation: 62246
Visitor lets you define a new operation without changing the classes of the elements on which it operates.
In code provided I don't see that you need to change an object in order to fit your needs, what Visitor
basically does, is change object state by using provided Properties
/Methods
/Fields
of object itself.
So, by me your code can fit Visitor pattern, if that was actually question, also cause patterns are guidelines and not rigid rules.
I personally would chose second way, as it much more clear and OOP oriented.
Regards.
Upvotes: 5
Reputation: 160862
I don't see the need for a visitor here at all - your approach perfectly illustrates that you can solve this problem just with polymorphism using a CalculateInterestAmount
method the subclasses can implement.
In my opinion you really need a very compelling case to consider using the Visitor pattern - most of the time other solutions are more straightforward and fit more naturally.
Having said that, your version 2 is really just using polymorphism - there's no benefit of using a Visitor this way. Version 1 illustrates the "double dispatch" approach of a visitor better, this is how a visitor generally works.
Upvotes: 4