Wednesday, February 11, 2015

Visitor Design Pattern

Design Pattern > Behavioral Design Pattern > Visitor Design Pattern

As per GOF the intention of visitor pattern is to "Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates"


Let's understand it with an example. Suppose, you have created a basic banking application and your account structure is following :

Interface Account {

    int withdraw(int amount);
    int deposit(int amount);
}

class CheckingAccount Implements Account {
    String AccountType;
    int AccountBalance;

// getter setter

int withdraw(int amount){
// business logic
}

int deposit(int amount){
// business logic
}
}

class SavingsAccount Implements Account {
    String AccountType;
    int AccountBalance;

// getter setter

int withdraw(int amount){
// business logic
}

int deposit(int amount){
// business logic
}
}

So, client can perform deposit and withdrawal to their account, assume a new requirement arrive from the bank that offers all account holders to invest their say 10 percent money from savings account in a new Investment Account and bank needs to calculate total invested amount from across all the account.
One way you can modify the Account interface with doInvest operation and change all it's type classes structure. like below :

Interface Account {

    int withdraw(int amount);
    int deposite(int amount);

    int doInvest(int amount);
}

class CheckingAccount Implements Account {

// existing code

int doInvest(int amount){

// business logic
}

}

class SavingsAccount Implements Account {

// existing code

int doInvest(int amount){

// business logic
}

}

The problem in this approach is that in the feauture if new requirement come then you have to again change all the class structure.
Visitor pattern helps in these kind of situation.

Solution :
In visitor Pattern we design Element Interface( in our example Account interface) in suach a way so that it shouold not be changed with new requirements.Then the main interface Visitor whose objective is to perform action on Element classes.Visitor is desigend as per new requirement.

the key terms in this pattern are -
Element -

  • An Interface ( Account) with accept method whose parameter is a Visitor 

Concrete Element -

  • Implements Element interface
  • Calls visit method of Visitor object

Visitor -

  • An interface with visit method whose patameter is an Element

Concrete Visitor -

  • Implements Visitor interface
  • Actual operation (doInvest ) happens in visit method

ClientClass -

  • Create Element objects
  • Create Visitor object
  •        call accept method of elements as per requirement



Java Implementation :

/**
 *  Element interface
 */
package com.kmingle.visitor;

public interface Account {

double accept(AccountVisitor visitor);
}


/**
 * Concrete Element class
 */
package com.kmingle.visitor;

public class CheckingAccount implements Account {

private String accountType;
private int AccountBalance;

public CheckingAccount(String acType, int acBal){
this.accountType = acType;
this.AccountBalance = acBal;
}

public String getAccountType() {
return accountType;
}


public void setAccountType(String accountType) {
this.accountType = accountType;
}


public int getAccountBalance() {
return AccountBalance;
}


public void setAccountBalance(int accountBalance) {
AccountBalance = accountBalance;
}


/* (non-Javadoc)
* @see com.kmingle.visitor.Account#accept(com.kmingle.visitor.AccountVisitor)
*/
@Override
public double accept(AccountVisitor visitor) {

return visitor.visit(this);
}

}


/**
 * Concrete Element class
 */
package com.kmingle.visitor;

public class SavingsAccount implements Account {

private String accountType;
private int AccountBalance;

public SavingsAccount(String acType, int acBal){
this.accountType = acType;
this.AccountBalance = acBal;
}

public String getAccountType() {
return accountType;
}


public void setAccountType(String accountType) {
this.accountType = accountType;
}


public int getAccountBalance() {
return AccountBalance;
}


public void setAccountBalance(int accountBalance) {
AccountBalance = accountBalance;
}


/* (non-Javadoc)
* @see com.kmingle.visitor.Account#accept(com.kmingle.visitor.AccountVisitor)
*/
@Override
public double accept(AccountVisitor visitor) {

return visitor.visit(this);
}

}


/**
 *  Visitor interface
 */
package com.kmingle.visitor;

public interface AccountVisitor {

double visit(Account acount);

}


/**
 * Concrete Visitor Class
 */
package com.kmingle.visitor;

public class InvestmentAccountVisitor implements AccountVisitor {

/* (non-Javadoc)
* @see com.kmingle.visitor.AccountVisitor#visit(com.kmingle.visitor.Account)
*/
@Override
public double visit(Account account) {

double investedAmount = 0;

String className = account.getClass().getSimpleName();

if(className.equals("SavingsAccount")){
System.out.println("Account Type is SavingsAccount");
investedAmount = ((SavingsAccount)account).getAccountBalance()*0.1;
System.out.println("savings account balance = "+ ((SavingsAccount)account).getAccountBalance());
System.out.println("invested amount = "+ investedAmount);
return investedAmount;
}
else if(className.equals("CheckingAccount")){
System.out.println("Account Type is CheckingAccount");
System.out.println("can't invest from checking account");
}
else{
System.out.println("invalid account");
}
return 0;

}

}


/**
 * Visitor Client
 */
package com.kmingle.visitor;

import java.beans.Visibility;
import java.util.ArrayList;

public class InvestmentVisitorClient {

public static void main(String[] args) {

ArrayList accountList = new ArrayList();

accountList.add(new CheckingAccount("checking", 100));

accountList.add(new SavingsAccount("savings", 200));

accountList.add(new SavingsAccount("savings", 500));

double totalInvesteAmount = calculateInvestedAmount(accountList);

System.out.println("Total Invested Amount = "+ totalInvesteAmount);
}

private static double calculateInvestedAmount(ArrayList accountList) {

AccountVisitor visitor = new InvestmentAccountVisitor();

double investedAmount = 0;

for(Account account : accountList){

investedAmount = investedAmount + account.accept(visitor);
}

return investedAmount;
}

}


Output :

Account Type is CheckingAccount
can't invest from checking account
Account Type is SavingsAccount
savings account balance = 200
invested amount = 20.0
Account Type is SavingsAccount
savings account balance = 500
invested amount = 50.0
Total Invested Amount = 70.0


Further, you can create as many visitors as per your requirement without changing the structure of Element interface/classes.

No comments:

Total Pageviews