Skip to main content

Separate Constructing a System from Using It


In the real world, in order to use a building (hotel, supermarket, etc) we need to construct it first. This concern should be applied for software development as well.

Step by step, I would like to show you the issue about no separation of constructing and using it and then I'll give you some approaches to overcome this issue.

| Note: you can find the below demonstrated code here  

Take a Look the Following Simple Application

Used tools and technologies: Eclipse (Mars), JDK 1.8

I had an App which uses Controller. Controller uses Service (an interface). Finally, Service has one concrete class is DefaultService.
//package vn.nvanhuong.system.separationconstructing;
public class App {
 public static void main(String[] args) {
  Controller controller = new Controller();
  controller.doAction();
 }
}

public class Controller {
 private Service service;
 
 public void doAction(){
  System.out.println("doAction in Controller");
  getService().execute();
 }
 
 public Service getService(){
  if(service == null){
   service = new DefaultService();
  }
  return service;
 }
}

public interface Service {
 void execute();
}

public class DefaultService implements Service {

 @Override
 public void execute() {
  System.out.println("executing Default Service");
 }

}

Do you find any issue?

Okay. Now, we just pay attention on this piece of code.
public Service getService(){
 if(service == null){
  service = new DefaultService();
 }
 return service;
}

Have you ever thought that we might meet at least three following issues?
  • Compile error: if Controller could not find DefaultService.
  • Testing: if we want to test Controller, we need to mock DefaultService in case it is a heavyweight object
  • Right object: if Service has more concrete classes, DefaultService will not be always a desired object in all cases.
The root cause is that we had a hard-coded "DefaultService" as a dependency of Controller.

How to solve?

The problem is the solution! Instead, we construct the instance of Service at Controller, we move this code of constructing into another place. Yeah!

There are three approaches.

Separation of Main - Separating the constructing into "main"

//package vn.nvanhuong.system.separationconstructing.main;

public class App {
 public static void main(String[] args) {
  Service service = new DefaultService();
  Controller controller = new Controller(service);
  controller.doAction();
 }
}

public class Controller {
 private Service service;

 public Controller(Service service) {
  this.service = service;
 }

 public void doAction() {
  System.out.println("doAction in Controller");
  service.execute();
 }
}

Factories - Using design pattern "Abstract Factory"

We move the constructing into a place separating from the application code.

//package vn.nvanhuong.system.separationconstructing.factory;

public class App {
 public static void main(String[] args) {
  ServiceFactory factory = new ServiceFactory();
  Service service = factory.getService(ServiceName.CUSTOM);
  Controller controller = new Controller(service);
  controller.doAction();
 }
}

public class ServiceFactory {

 public Service getService(ServiceName serviceName) {
  if (Objects.isNull(serviceName)) {
   throw new IllegalArgumentException("service name must not be null");
  }

  switch (serviceName) {
  case DEFAULT:
   return new DefaultService();
  case CUSTOM:
   return new CustomService();
  default:
   return null;
  }
 }

}

public enum ServiceName {
 DEFAULT, CUSTOM
}

public class CustomService implements Service {

 @Override
 public void execute() {
  System.out.println("executing from Custom Service");

 }

}

public class Controller {
 private Service service;

 public Controller(Service service) {
  this.service = service;
 }

 public void doAction() {
  System.out.println("doAction in Controller");
  service.execute();
 }
}

Dependency Injection

We move the second reposibility of object Controller (constructing Service) into another object. Moreover, DI helps us solve dependencies passively.

I used Google Guice for this demonstration. Because I still wanted to use a Java project (in Eclipse) instead of Maven project so that I simply found needed jars on Maven Repository and included them into project's buildpath. They are "guice-3.0.jar", "cglib-2.2.1.jar", "javax.inject-1.jar" and ''aopalliance-1.0.jar".

//package vn.nvanhuong.system.separationconstructing.di;

public class App {
 public static void main(String[] args) {
  Injector injector = Guice.createInjector(new AppInjector());
  Controller controller = injector.getInstance(Controller.class);
  controller.doAction();
 }
}

public class AppInjector extends AbstractModule {

 @Override
 protected void configure() {
  bind(Service.class).to(CustomService.class);
 }

}

public class Controller {
 private Service service;

 public void doAction() {
  System.out.println("doAction in Controller");
  service.execute();
 }

 @Inject
 public void setService(Service service) {
  this.service = service;
 }
}

How do you think about the issues and solutions? Leave your comments down below. :)

--------------
References:

[1]. Robert C. Martin, Clean Code - A Handbook of Agile Software Craftsmanship, Chapter 11, Systems.
[2]. https://en.wikipedia.org/wiki/Abstract_factory_pattern
[3]. http://www.journaldev.com/2403/google-guice-dependency-injection-example-tutorial

Comments

Popular posts from this blog

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

Junit - Test fails on French or German string assertion

In my previous post about building a regex to check a text without special characters but allow German and French . I met a problem that the unit test works fine on my machine using Eclipse, but it was fail when running on Jenkins' build job. Here is my test: @Test public void shouldAllowFrenchAndGermanCharacters(){ String source = "ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ"; assertFalse(SpecialCharactersUtils.isExistSpecialCharater(source)); } Production code: public static boolean isExistNotAllowedCharacters(String source){ Pattern regex = Pattern.compile("^[a-zA-Z_0-9_ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ]*$"); Matcher matcher = regex.matcher(source); return !matcher.matches(); } The result likes the following: Failed tests: SpecialCharactersUtilsTest.shouldAllowFrenchAndGermanCharacters:32 null A guy from stackoverflow.com says: "This is probably due to the default encoding used for your Java source files. The ö in the string literal in the J...

JSF, Primefaces - Invoking Application Code Even When Validation Failed

A use case I have a form which has requirements as follow: - There are some mandatory fields. - Validation is triggered when changing value on each field. - A button "Next" is enable only when all fields are entered. It turns to disabled if any field is empty. My first approach I defined a variable "isDisableNext" at a backend bean "Controller" for dynamically disabling/enabling the "Next" button by performing event "onValueChange", but, it had a problem: <h:form id="personForm"> <p:outputLabel value="First Name" for="firstName"/> <p:inputText id="firstName" value="#{person.firstName}" required="true"> <p:ajax event="change" listener="#{controller.onValueChange}" update="nextButton"/> </p:inputText> <p:outputLabel value="Last Name" for="lastName"/> <p:i...

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 I did customize "rasa-nlu-trainer" as my own tool

Check out my implementation here Background I wanted to have a tool for human beings to classify intents and extract entities of texts which were obtained from a raw dataset such as Rocket.chat's conversation, Maluuba Frames or  here . Then, the output (labeled texts) could be consumed by an NLU tool such as Rasa NLU. rasa-nlu-trainer was a potential one which I didn't need to build an app from scratch. However, I needed to add more of my own features to fulfill my needs. They were: 1. Loading/displaying raw texts stored by a database such as MongoDB 2. Manually labeling intents and entities for the loaded texts 3. Persisting labeled texts into the database I firstly did look up what rasa-nlu-trainer 's technologies were used in order to see how to implement my mentioned features. At first glance rasa-nlu-trainer was bootstrapped with Create React App. Create React App is a tool to create a React app with no build configuration, as it said. This too...