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.8I 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.
Have you ever thought that we might meet at least three following issues?public Service getService(){
if(service == null){
service = new DefaultService();
}
return service;
}
- 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.
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