In this
post, I am going to explain how to decouple the implementation with interfaces
using ServiceLoader class and module system.
In a
typical application, there is a well-defined interface and multiple
implementations of the interface available. Instead of tightly coupling the
application with both interfaces and implementation classes, we can decouple
them like below.
a.
Interfaces
are defined in a module ‘app.interfaces’.
b.
All
the respective implementations of interfaces defined in their respective modules
‘app.interfaces.implementation1’, ‘app.interfaces.implementation2’, etc.
c.
All
the implementations register themselves in service registery.
d.
Main
application asks service registry to give the implementation classes of given
interfaces. Service registry gives the implementation classes of this
interface.
With this
approach, main application only needs to be aware of interface module, it no
need to worry about from where the implementation come from. It is the
responsibility of ServiceLoader class to load give an implemented object of
this interface.
Let’s see
it with an example.
$tree services_example/ services_example/ └── src └── app.interfaces ├── com │ └── sample │ └── app │ └── interfaces │ └── Circle.java └── module-info.java 6 directories, 2 files
Circle.java
package com.sample.app; import com.sample.util.ArithmeticUtil; public class App { public static void main(String args[]) { System.out.println("Sum of 10 and 20 is : " + ArithmeticUtil.add(10, 20)); System.out.println("Subtraction of 10 and 20 is : " + ArithmeticUtil.sub(10, 20)); System.out.println("Multiplication of 10 and 20 is : " + ArithmeticUtil.mul(10, 20)); System.out.println("Division of 10 and 20 is : " + ArithmeticUtil.div(10, 20)); } }
module-info.java
module app.interfaces{ exports com.sample.app.interfaces; }
Step 2: Create ‘app.interfaces.impl’ module by following below directory structure.
$tree services_example/ services_example/ └── src ├── app.interfaces │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── Circle.java │ └── module-info.java └── app.interfaces.impl ├── com │ └── sample │ └── app │ └── interfaces │ └── impl │ └── CircleImpl.java └── module-info.java
CircleImpl.java
package com.sample.app.interfaces.impl; import com.sample.app.interfaces.Circle; public class CircleImpl implements Circle { public static final double PI = 3.14; @Override public double perimeter(double radius) { return 2 * PI * radius; } @Override public double area(double radius) { return PI * radius * radius; } }
module-info.java
module app.interfaces.impl{ requires app.interfaces; provides com.sample.app.interfaces.Circle with com.sample.app.interfaces.impl.CircleImpl; }
Provides
‘interface name’ with ‘implementation class’ means that I am providing service
to Circle interface via CircleImpl class.
Step 3:
Create ‘app.main’
module by following below directory structure.
$tree services_example/ services_example/ └── src ├── app.interfaces │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── Circle.java │ └── module-info.java ├── app.interfaces.impl │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── impl │ │ └── CircleImpl.java │ └── module-info.java └── app.main ├── com │ └── sample │ └── app │ └── App.java └── module-info.java 16 directories, 6 files
App.java
package com.sample.app; import java.util.ServiceLoader; import com.sample.app.interfaces.Circle; public class App { public static void main(String args[]) { Iterable<Circle> iterable = ServiceLoader.load(Circle.class); System.out.println("Going to get instances"); for(Circle circle : iterable) { double area = circle.area(10); double perimeter = circle.perimeter(10); System.out.println("Area : " + area); System.out.println("Perimeter : " + perimeter); } } }
module-info.java
module app.main{ requires app.interfaces; uses com.sample.app.interfaces.Circle; }
‘uses’
tells that this application use Circle interface.
Step 4:
Compile App.java and
CircleImpl.java files.
javac
--module-source-path src -d out src/app.main/com/sample/app/App.java
javac
--module-source-path src -d out
src/app.interfaces.impl/com/sample/app/interfaces/impl/CircleImpl.java
Total
directory structure will change like below.
$tree . ├── out │ ├── app.interfaces │ │ ├── com │ │ │ └── sample │ │ │ └── app │ │ │ └── interfaces │ │ │ └── Circle.class │ │ └── module-info.class │ ├── app.interfaces.impl │ │ ├── com │ │ │ └── sample │ │ │ └── app │ │ │ └── interfaces │ │ │ └── impl │ │ │ └── CircleImpl.class │ │ └── module-info.class │ └── app.main │ ├── com │ │ └── sample │ │ └── app │ │ └── App.class │ └── module-info.class └── src ├── app.interfaces │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── Circle.java │ └── module-info.java ├── app.interfaces.impl │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── impl │ │ └── CircleImpl.java │ └── module-info.java └── app.main ├── com │ └── sample │ └── app │ └── App.java └── module-info.java 32 directories, 12 files
Step 5:
Run App.java by
executing below command.
$java --module-path out -m app.main/com.sample.app.App Going to get instances Area : 314.0 Perimeter : 62.800000000000004
Adding
another implementation
You can
add other implementation to existing project. ServiceLoader will pick this
implementation also in run time.
Create new
module ‘app.interfaces.otherimpl’ like below.
$tree src/app.interfaces.otherimpl/ src/app.interfaces.otherimpl/ ├── com │ └── sample │ └── app │ └── interfaces │ └── otherimpl │ └── CircleImpl.java └── module-info.java 5 directories, 2 files
CircleInfo.java
package com.sample.app.interfaces.otherimpl; import com.sample.app.interfaces.Circle; public class CircleImpl implements Circle { public static final double PI = 3.14285714285; @Override public double perimeter(double radius) { return 2 * PI * radius; } @Override public double area(double radius) { return PI * radius * radius; } }
Module-info.java
module app.interfaces.otherimpl{ requires app.interfaces; provides com.sample.app.interfaces.Circle with com.sample.app.interfaces.otherimpl.CircleImpl; }
Compile
CircleImpl.java using below command.
javac
--module-source-path src -d out
src/app.interfaces.otherimpl/com/sample/app/interfaces/otherimpl/CircleImpl.java
$tree services_example/ services_example/ ├── out │ ├── app.interfaces │ │ ├── com │ │ │ └── sample │ │ │ └── app │ │ │ └── interfaces │ │ │ └── Circle.class │ │ └── module-info.class │ ├── app.interfaces.impl │ │ ├── com │ │ │ └── sample │ │ │ └── app │ │ │ └── interfaces │ │ │ └── impl │ │ │ └── CircleImpl.class │ │ └── module-info.class │ ├── app.interfaces.otherimpl │ │ ├── com │ │ │ └── sample │ │ │ └── app │ │ │ └── interfaces │ │ │ └── otherimpl │ │ │ └── CircleImpl.class │ │ └── module-info.class │ └── app.main │ ├── com │ │ └── sample │ │ └── app │ │ └── App.class │ └── module-info.class └── src ├── app.interfaces │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── Circle.java │ └── module-info.java ├── app.interfaces.impl │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── impl │ │ └── CircleImpl.java │ └── module-info.java ├── app.interfaces.otherimpl │ ├── com │ │ └── sample │ │ └── app │ │ └── interfaces │ │ └── otherimpl │ │ └── CircleImpl.java │ └── module-info.java └── app.main ├── com │ └── sample │ └── app │ └── App.java └── module-info.java 44 directories, 16 files
Execute
App.java.
$java --module-path out -m app.main/com.sample.app.App Going to get instances Area : 314.285714285 Perimeter : 62.857142857 Area : 314.0 Perimeter : 62.800000000000004
You can
download complete project source code from below git location.
No comments:
Post a Comment