A couple of months in the past I made a small Java library, which is price explaining for the reason that design of its courses and interfaces is fairly uncommon. It’s very a lot object-oriented for a fairly crucial job: constructing a pipeline of doc transformations. The objective was to do that in a declarative and immutable means, and in Java. Nicely, as a lot because it’s attainable.
![Barfuss (2005) by Til Schweiger](https://www.yegor256.com/images/2022/08/barfuss.jpg)
Let’s say you could have a doc, and you’ve got a group of transformations, every of which can do one thing with the doc. Every transformation, for instance, is a small piece of Java code. You need to construct a listing of transformations after which cross a doc by this record.
First, I made an interface Shift
(as an alternative of the regularly used and boring “transformation”):
interface Shift {
Doc apply(Doc doc);
}
Then I made an interface Train
(that is the identify I made up for the gathering of transformations) and its default implementation:
interface Prepare {
Prepare with(Shift shift);
Iterator<Shift> iterator();
}
class TrDefault implements Prepare {
personal closing Iterable<Shift> record;
@Override
Prepare with(Shift shift) {
closing Assortment<Shift> objects = new LinkedList<>();
for (closing Shift merchandise : this.record) {
objects.add(merchandise);
}
objects.add(shift);
return new TrDefault(objects);
}
@Override
public Iterator<Shift> iterator() {
return this.record.iterator();
}
}
Ah, I forgot to let you know. I’m an enormous fan of immutable objects. That’s why the Prepare
doesn’t have a technique add
, however as an alternative has with
. The distinction is that add
modifies the item, whereas with
makes a brand new one.
Now, I can construct a practice of shifts with TrDefault
, a easy default implementation of Prepare
, assuming ShiftA
and ShiftB
are already carried out:
Prepare practice = new TrDefault()
.with(new ShiftA())
.with(new ShiftB());
Then I created an Xsline
class (it’s “XSL” + “pipeline”, since in my case I’m managing XML paperwork and rework them utilizing XSL stylesheets). An occasion of this class encapsulates an occasion of Prepare
after which passes a doc by all its transformations:
Doc enter = ...;
Doc output = new Xsline(practice).cross(enter);
Up to now so good.
Now, I need all my transformations to log themselves. I created StLogged
, a decorator of Shift
, which encapsulates the unique Shift
, decorates its methodology apply
, and prints a message to the console when the transformation is accomplished:
class StLogged implements Shift {
personal closing Shift origin;
@Override
Doc apply(Doc earlier than) {
Doc after = origin.apply(earlier than);
System.out.println("Transformation accomplished!");
return after;
}
}
Now, I’ve to do that:
Prepare practice = new TrDefault()
.with(new StLogged(new ShiftA()))
.with(new StLogged(new ShiftB()));
Appears to be like like a duplication of new StLogged(
, particularly with a group of some dozen shifts. To do away with this duplication I created a decorator for Prepare
, which on the fly decorates shifts that it encapsulates, utilizing StLogged
:
Prepare practice = new TrLogged(new TrDefault())
.with(new ShiftA()))
.with(new ShiftB());
In my case, all shifts are doing XSL transformations, taking XSL stylesheets from recordsdata accessible in classpath. That’s why the code seems to be like this:
Prepare practice = new TrLogged(new TrDefault())
.with(new StXSL("stylesheet-a.xsl")))
.with(new StXSL("stylesheet-b.xsl")));
There’s an apparent duplication of new StXSL(...)
, however I can’t merely do away with it, for the reason that methodology with
expects an occasion of Shift
, not a String
. To resolve this, I made the Prepare
generic and created TrClasspath
decorator:
Prepare<String> practice = new TrClasspath<>(new TrDefault<>())
.with("stylesheet-a.xsl"))
.with("stylesheet-b.xsl"));
TrClasspath.with()
accepts String
, turns it into StXSL
and passes to TrDefault.with()
.
Take note of the snippet above: the practice
is now of sort Prepare<String>
, not Prepare<Shift>
, as could be required by Xsline
. The query now’s: how can we get again to Prepare<Shift>
?
Ah, I forgot to say. I wished to design this library with one vital precept in thoughts, suggested in 2014: all objects could solely implement strategies from their interfaces. That’s why, I couldn’t simply add a technique getEncapsulatedTrain()
to TrClasspath
.
I launched a brand new interface Train.Temporary<T>
with a single methodology again()
returning Prepare<T>
. The category TrClasspath
implements it and I can do that:
Prepare<Shift> practice = new TrClasspath<>(new TrDefault<>())
.with("stylesheet-a.xsl"))
.with("stylesheet-b.xsl"))
.again();
Subsequent I made a decision to do away with the duplication of .with()
calls. Clearly, it will be simpler to have the flexibility to offer a listing of file names as an array of String
and construct the practice from it. I created a brand new class TrBulk
, which does precisely that:
Iterable<String> names = Arrays.asList(
"stylesheet-a.xsl",
"stylesheet-b.xsl"
);
Prepare<Shift> practice = new TrBulk<>(
new TrClasspath<>(
new TrDefault<>()
)
).with(names).again();
With this design I can assemble the practice in nearly any attainable means.