Wednesday, February 29, 2012

How to eliminate If-Statements and InstanceOf with the Visitor-Pattern

Using the visitor pattern to eliminate If-Statements and InstanceOf


We all know the visitor pattern from the book GoF (gang of four) or from other famous books. The examples in there are usually associated with trees or composite structures. I was always searching for real life examples, where this pattern could be used, making software reusable and more readable. During a refactoring task, a collegue of mine had a really good idea, that i want to share with you.


Here is the code fragment from the method we want to refactore. The method has a lot of if-statements and instanceOf in there. It maps contacts to data transfer objects (DTO)


public static void mapKontakte(ContactType contactType, List<ErweiterteKontakt<? extends StringDomainEnum>> kontakte) {
        ch.abraxas.tax.register.registerimport.parser.ech0046.v10.ObjectFactory ech0046Factory =
                new ch.abraxas.tax.register.registerimport.parser.ech0046.v10.ObjectFactory();
        for (ErweiterteKontakt<? extends StringDomainEnum> kontakt : kontakte) {
            if (kontakt instanceof Telefon) {
                PhoneType phoneType = ech0046Factory.createPhoneType();
                phoneType.setPhoneCategory(new BigInteger( ((Telefon) kontakt).getKategorie().key()));
                phoneType.setPhoneNumber(kontakt.getDetail());
                contactType.getPhone().add(phoneType);
            } else if (kontakt instanceof Email) {
                EmailType emailType = ech0046Factory.createEmailType();
                emailType.setEmailCategory(new BigInteger( ((Email) kontakt).getKategorie().key()));
                emailType.setEmailAddress(kontakt.getDetail());
                contactType.getEmail().add(emailType);
            } else if (kontakt instanceof Internet) {
                InternetType internetType = ech0046Factory.createInternetType();
                internetType.setInternetCategory(new BigInteger( ((Internet) kontakt).getKategorie().key()));
                internetType.setInternetAddress(kontakt.getDetail());
                contactType.getInternet().add(internetType);
            }
            // Should we use an else branch to verify that we are mapping all Kontakte?
        }
    }


here is the same method after refactoring:


public static void mapKontakte(ContactType contactType,
   List<ErweiterteKontakt<? extends StringDomainEnum>> kontakte) {

  ErweiterterKontaktVisitor visitor = new BindingObjectFromKontaktVisitor(contactType);
  for (ErweiterteKontakt<? extends StringDomainEnum> kontakt : kontakte) {
   // calls the Kontakt to use the visitor
   kontakt.accept(visitor);
  }
 }


Well here is how to do it. In the abstract class ErweiterteKontakt we define following method:


public abstract void accept(ErweiterterKontaktVisitor visitor);


Then we implement this abstract method in the classes Email, Telefon and Internet. They all extends ErweiterkeKontakt.


 @Override
 public void accept(ErweiterterKontaktVisitor visitor) {
  visitor.visit(this);
 }

Defining the visitor interface


public interface ErweiterterKontaktVisitor {

 /**
  * Visit the telefon
  * 
  * @param telefon
  */
 public void visit(Telefon telefon);

 /**
  * visit the email
  * 
  * @param email
  */
 public void visit(Email email);

 /**
  * visit the internet
  * 
  * @param internet
  */
 public void visit(Internet internet);
}


Then Implementing the Visitor itself:


public class BindingObjectFromKontaktVisitor implements ErweiterterKontaktVisitor {

 private ch.abraxas.tax.register.registerimport.parser.ech0046.v10.ObjectFactory ech0046Factory = new ch.abraxas.tax.register.registerimport.parser.ech0046.v10.ObjectFactory();

 private ContactType contactType;

 /**
  * Konstruktor
  */
 public BindingObjectFromKontaktVisitor(ContactType contactType) {
  this.contactType = contactType;
 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void visit(Telefon telefon) {
  PhoneType phoneType = ech0046Factory.createPhoneType();
  phoneType.setPhoneCategory(new BigInteger(telefon.getKategorie().key()));
  phoneType.setPhoneNumber(telefon.getDetail());
  contactType.getPhone().add(phoneType);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void visit(Email email) {
  EmailType emailType = ech0046Factory.createEmailType();
  emailType.setEmailCategory(new BigInteger(email.getKategorie().key()));
  emailType.setEmailAddress(email.getDetail());
  contactType.getEmail().add(emailType);

 }

 /**
  * {@inheritDoc}
  */
 @Override
 public void visit(Internet internet) {
  InternetType internetType = ech0046Factory.createInternetType();
  internetType.setInternetCategory(new BigInteger((internet).getKategorie().key()));
  internetType.setInternetAddress(internet.getDetail());
  contactType.getInternet().add(internetType);

 }
}


Done!
No more If-Statements, no more InstanceOf and the code is now extandable and more flexible.

😱👇 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO ðŸ˜±ðŸ‘‡

Be sure to read, it will change your life!
Show your work by Austin Kleonhttps://amzn.to/34NVmwx

This book is a must read - it will put you in another level! (Expert)
Agile Software Development, Principles, Patterns, and Practiceshttps://amzn.to/30WQSm2

Write cleaner code and stand out!
Clean Code - A Handbook of Agile Software Craftsmanship: https://amzn.to/33RvaSv

This book is very practical, straightforward and to the point! Worth every penny!
Kotlin for Android App Development (Developer's Library): https://amzn.to/33VZ6gp

Needless to say, these are top right?
Apple AirPods Pro: https://amzn.to/2GOICxy

😱👆 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO ðŸ˜±ðŸ‘†