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

BIRT - Fix the size of an image

I use a dynamic image as a logo my report in pdf. At the beginning, I use table to align the logo in left or right. I meet a problem with some images with a large width or height. My customer requires that the logo should be displayed in original size. These following steps solves my problem: 1. Use Grid instead of Table 2. Set Grid "Height" is 100%  and "Width" is blank 3. Set "Fit to container" for images are "true". Download the the template here .

Google I/O 2017 Notes

WOW! How meaningful this below video explains about the name of  "I/O". Sundar Pichai talked a lot of Machine Learning Machine Learning is a very hot trend these days. Google uses it for their products. Google Assistant: Easily booking an online meal by talking with Google Assistant like a staff of partners, for example. Google Home: Hands-free calling. Google Photos: sharing suggestion, shared library, photo books and google lens. Youtube: 360 degree video, live stream. Kotlin became an official programming language for Android https://kotlinlang.org I'm on the way to Kotlin! ^^ Reference: [1]. https://www.youtube.com/watch?v=Y2VF8tmLFHw

JSF 2 - Dynamically manipulating the component tree with system events

Let's suppose we want to modify the metadata (attributes)  of elements such as render , requried , maxlength but we do not define in JSF tags. The manipulating components can be conducted in Drools  files, for example. How could we do? I think that is what we need to change something of component tree during JSF life-cycle. JSF supports event handling throughout the JSF life-cycle. In this post, I use two events: postAddToView for scanning components tree and preRenderView for manipulating the meta of components before rendering to GUI. I modified my own project from previous post for this example. This is my first further JSF trying out with the project as I said before. :) We define the tags f:event below the form - a container component of the components which we want to work on. The valid values for the attribute type for f:event can be found from tag library document  of JSF 2. <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" x...

Styling Sort Icons Using Font Awesome for Primefaces' Data Table

So far, Primefaces has used image sprites for displaying the sort icons. This leads to a problem if we want to make a different style for these icons; for example, I would make the icon "arrow up" more blurry at the first time the table loading because I want to highlight the icon "arrow down". I found a way that I can replace these icons with Font Awesome icons. We will use "CSS Pseudo-classes" to achieve it. The hardest thing here is that we should handle displaying icons in different cases. There is a case both "arrow up" and "arrow down" showing and other case is only one of these icons is shown. .ui-sortable-column-icon.ui-icon.ui-icon-carat-2-n-s { background-image: none; margin-left: 5px; font-size: 1.1666em; position: relative; } .ui-sortable-column-icon.ui-icon.ui-icon-carat-2-n-s:not(.ui-icon-triangle-1-s)::before { content: "\f106"; font-family: "FontAwesome"; position: ...

Math fundamentals and Katex

It was really tough for me to understand many articles about data science due to the requirements of understanding mathematics (especially linear algebra). I’ve started to gain some basic knowledges about Math by reading a book first. The great tool Typora and stackedit with supporting Katex syntax simply helps me to display Math-related symbols. Let’s start! The fundamental ideas of mathematics: “doing math” with numbers and functions. Linear algebra: “doing math” with vectors and linear transformations. 1. Solving equations Solving equations means finding the value of the unknown in the equation. To find the solution, we must break the problem down into simpler steps. E.g: x 2 − 4 = 4 5 x 2 − 4 + 4 = 4 5 + 4 x 2 = 4 9 x = 4 9 ∣ x ∣ = 7 x = 7  or  x = − 7 \begin{aligned} x^2 - 4 &= 45\\ x^2 - 4 + 4 &= 45 + 4\\ x^2 &= 49\\ \sqrt{x}&=\sqrt{49}\\ |x| &= 7\\ x=7 &\text{ or } x=-7 \end{aligned} x 2 − 4 x 2 − 4 + 4 x 2 x ​ ∣ x ∣ x = 7 ​ = 4 5 = 4 ...