↗️ This README only covers one section of the demo. The master branch contains more information.
Jigsaw enables loose coupling by implementing the service locator pattern, where the module system itself acts as the locator.
Somebody who recently read a blog post about how cool loose coupling is looked at our code from the previous section and complained about the tight relationship between main and factories.
To be more precise she critized that main even knows factories.
And just because it has to instantiate some implementations of a perfectly fine abstraction (the SurpriseFactory
)!
Having a man in the middle providing us with these implementing instances would remove the direct dependency. Even better, if said middleman would be able to find all implementations on the module path, the calendar's surprises could easily be configured by adding or removing modules before launching.
This is indeed possible with Jigsaw.
We can have a module specify that it provides implementations of an interface.
Another module can express that it uses said interface and find all implementations with the [ServiceLocator
] (for details see main below).
We use this opportunity to split factories into chocolate and quote and end up with these modules and dependencies:
- surprise -
Surprise
andSurpriseFactory
- calendar - the calendar, which uses the surprise API
- chocolate - the
ChocolateFactory
as a service - quote - the
QuoteFactory
as a service - main - the application; no longer requires individual factories
The first step is to reorganize the source code.
The only change from before is that src/org.codefx.demo.advent.factories
is replaced by src/org.codefx.demo.advent.factory.chocolate
and src/org.codefx.demo.advent.factory.quote
.
Lets look at the individual modules.
Nothing changed from before.
Dito.
As before with factories this module must require the surprise module.
More interesting are its exports.
It provides an implementation of SurpriseFactory
, namely ChocolateFactory
, which is specified as follows:
provides org.codefx.demo.advent.surprise.SurpriseFactory
with org.codefx.demo.advent.factory.chocolate.ChocolateFactory;
Since this class is the entirety of its public API it does not need to export anything else. Hence no other export clause is necessary.
We end up with:
module org.codefx.demo.advent.factory.chocolate {
// list the required modules
requires transitive org.codefx.demo.advent.surprise;
// specify which class provides which service
provides org.codefx.demo.advent.surprise.SurpriseFactory
with org.codefx.demo.advent.factory.chocolate.ChocolateFactory;
}
Compilation and packaging is straight forward:
javac -p mods -d classes/org.codefx.demo.advent.factory.chocolate ${source files}
jar -c --file mods/org.codefx.demo.advent.factory.chocolate.jar ${compiled class files}
Analog to chocolate.
The most interesting part about main is how it uses the ServiceLocator
to find implementation of SurpriseFactory
.
From its main
method:
List<SurpriseFactory> surpriseFactories = new ArrayList<>();
ServiceLoader.load(SurpriseFactory.class).forEach(surpriseFactories::add);
Our application now only requires calendar but must specifiy that it uses SurpriseFactory
.
It has no API to export.
module org.codefx.demo.advent {
// list the required modules
requires org.codefx.demo.advent.calendar;
// list the used services
uses org.codefx.demo.advent.surprise.SurpriseFactory;
// exports no functionality
}
Compilation and execution are like before.