Skip to main content

Make Our Code More Testable with Proxy Design Pattern


If you heard about the term separation of concerns, you could agree this concept is very important for making a system "clean". One of the most important characteristics of a clean system is testable.

Let me tell you my story about how I've just come acrosss the design pattern Proxy.

Note: to get started, you can find a very simple example of the pattern Proxy here

Let's start!

I have an interface as below:
public interface DocumentGenerator {
    File generate(Document document) throws BusinessException;
}

The following is my first implementation for a concrete class of DocumentGenerator.
public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 private void validateNotNullParams(LibertyDossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
  throws BusinessException {
  setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }
}

The client code constructs the concrete class DocumentGeneratorImpl.
DocumentGenerator  generator = new DocumentGeneratorImpl(dossier, locale);

I saw that some private methods "validateNotNullParams(dossier, locale)" and "setCobIdForDossierIfNotExist()" are just a second responsibility of class DocumentGeneratorImpl.

Follow "S" of SOLID  - Single Responsibility

Firstly, I was thinking about how to separate these methods into another class. Then, I create a class called DocumentGeneratorHelper which contains to these methods. It was just improved a bit.

public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public DocumentGeneratorImpl(Dossier dossier, Locale locale) {
  DocumentGeneratorHelper.validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  DocumentGeneratorHelper.setCobIdForDossierIfNotExist();
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

However, if I create tests for DocumentGeneratorImpl, I need to mock DocumentGeneratorHelper. Huh....! Any better approach?

I was thinking about that it is somehow I need to apply an Aspect-Oriented Programming (AOP) approach. As my google searching result, there are some approaches but they are quite complicated and heavy. They use Java Proxy or AOP Frameworks such as AspectJ.

A Simple AOP Approach

Fortunately, the keyword "proxy" leads me to the pattern Proxy. The idea of Proxy is really simple and cool!



Okay, now I create a Proxy instead of a Helper as before.

public class DocumentGeneratorProxy implements DocumentGenerator{
 private DocumentGenerator generator;
 private Dossier dossier;
 
 public DocumentGeneratorProxy(Dossier dossier, Locale locale) {
  validateNotNullParams(dossier, locale);
  this.dossier = dossier;
  this.generator = new DocumentGenerator(this.dossier, locale);
 }

 private void validateNotNullParams(Dossier dossier, Locale locale) {
  if(Objects.isNull(dossier)) {
   throw new IllegalArgumentException("Dossier must not be null!");
  }
  
  if(Objects.isNull(dossier.getDossierType())){
   throw new IllegalArgumentException("Dossier type must not be null!");
  }
  
  if(Objects.isNull(locale)) {
   throw new IllegalArgumentException("Locale must be defined!");
  }
 }

 @Override
 public File generate(Document document, boolean temporary)
   throws BusinessException {
  setCobIdForDossierIfNotExist();
  return generator.generate(document, temporary);
 }

 private void setCobIdForDossierIfNotExist() {
  if(StringUtils.isEmpty(dossier.getCobId())) {
   String generatedCobId = DossierService.getInstance().generateCobId();
   dossier.setCobId(generatedCobId);
  }
 }

}

Done! DocumentGeneratorImpl  doesn't contain its second responsibilities anymore. These methods are tested by the Proxy and we don't need to mock in DocumentGeneratorImpl.
the public class DocumentGeneratorImpl implements DocumentGenerator {
 private Dossier dossier;
 private Locale locale;
 
 public LibertyDocumentGenerator(Dossier dossier, Locale locale) {
  this.dossier = dossier;
  this.locale = locale;
 }
 
 @Override
 public File generate(Document document, boolean temporary) 
   throws BusinessException {
  switch (dossier.getDossierType()) {
  case TYPE_A:
   // Do some logic here in case TYPE_A
  case TYPE_BA:
   // Do some logic here in case TYPE_B
  default:
   throw new BusinessException("Not supported dossier type");
  }
 }
 
}

So, instead of calling directly DocumentGeneratorImpl, we call DocumentGeneratorProxy in the client code as below:

DocumentGenerator  generator = new DocumentGeneratorProxy(dossier, locale);

Happy codings!

Comments

Popular posts from this blog

Only allow input number value with autoNumeric.js

autoNumeric is a jQuery plugin that automatically formats currency and numbers as you type on form inputs. I used autoNumeric 1.9.21 for demo code. 1. Dowload autoNumeric.js file from  https://github.com/BobKnothe/autoNumeric 2. Import to project <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script type="text/javascript" src="js/autoNumeric.js"></script> 3. Define a function to use it <script type="text/javascript"> /* only number is accepted */ function txtNumberOnly_Mask() { var inputOrgNumber = $("#numberTxt"); inputOrgNumber.each(function() { $(this).autoNumeric({ aSep : '', aDec: '.', vMin : '0.00' }); }); } </script> 4. Call the function by event <form> <input type="text" value="" id="numberTxt"/>(only number) </form> <script ty...

How to apply Lean - Kanban for your business

This is the topic of Scrum Breakfast meetup this time, speaker: Ms. Phuong Bui - Technical Project Manager of YOOSE Pte. Ltd. http://www.meetup.com/Scrum-Breakfast-Vietnam-Agile-and-Scrum-Meetup/events/230313727/ Lean comes from Lean manufacturing is a method that focuses on elimination of wastes. In other words, this is a set of principles for archiving the quality, speed and customer alignment. The first time I knew about the term "Lean" is  from the book Software Craftsmanship . Sandro recommends if we want to transform our pet projects into a real business, we should get familiar with Lean Startup concepts. In this talk, Ms. Phuong pointed out some major wastes includes information (ex: unclear requirements), processes (ex: waiting), physical environment and people. Knowing what the problems should be the best way to eliminate them. The difference between  Single item flow and Batch processing is the second main point; and it is the Lean's idea. Batch pr...

Applying pipeline “tensorflow_embedding” of Rasa NLU

According to this nice article , there was a new pipeline released using a different approach from the standard one ( spacy_sklearn ). I wanted to give it a try to see whether it can help with improving bot’s accuracy. After applying done, I gave an evaluation of “tensorflow_embedding”. It seemed to work better a bit. For example, I defined intents “greet” and “goodbye” with some following messages in my training data. ## intent:greet - Hey! How are you? - Hi! How can I help you? - Good to see you! - Nice to see you! - Hi - Hello - Hi there ## intent:goodbye - Bye - Bye Bye - See you later - Take care - Peace In order to play around with Rasa NLU, I created a project here . You can have a look at this change from this pull request . Yay! When I entered message “hi bot”, then bot with “tensorflow_embedding” could detect intent “greet” with better confidence scores rather than bot with “spacy_sklearn”. The following are responses after executing curl -X POST loc...

The HelloWorld example of JSF 2.2 with Myfaces

I just did by myself create a very simple app "HelloWorld" of JSF 2.2 with a concrete implementation Myfaces that we can use it later on for our further JSF trying out. I attached the source code link at the end part. Just follow these steps below: 1. Create a Maven project in Eclipse (Kepler) with a simple Java web application archetype "maven-archetype-webapp". Maven should be the best choice for managing the dependencies , so far. JSF is a web framework that is the reason why I chose the mentioned archetype for my example. 2. Import dependencies for JSF implementation - Myfaces (v2.2.10) into file pom.xml . The following code that is easy to find from  http://mvnrepository.com/  with key words "myfaces". <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>2.2.10</version> </dependency> <dependency> <groupId>org.apache.myfaces.core<...

The Evolution of Team Collaboration

I n my point of view, if a team has a good collaboration, team members will achieve the following: To be more effective To make work more enjoyable I have been working for a company for nearly four years as an software developer. Working on various projects from maintaining existing systems to developing a substantial product resulted in me moving to new teams three times. Actually, my most stable team lasted only around three years.  Every time I've moved to a new team, I have a chance to work with new members and a new team culture again. Indeed, I realize that there is a process of developing the team collaboration which gets better time by time. I think it is an evolution ! Phase 1: Poorly collaborate For example, that is when the team members have the following issues: Only work on his/her area of expertise Don't communicate to others Be not confident to take on new challages Don't listen to other members. Subsequently, the team ha...