
This is my third post about Qi4j basics. In this post I'll show you how to implement behavior and wire it together with the code I already wrote for the previous post.
As you probably remember Qi4j manages composites. Composite is defined as a composition of fragments. Fragment is a piece of code responsible for concrete functionality. Each fragment can be categorized either as a mixin or modifier. Mixin's role is to provide an implementation for composite's methods. At the moment Rational composite has only properties. So let's add some behavior methods. (Mixins are not limited only to provide behavior implementation, in fact properties are managed by predefined mixin too)
Step 1. Extend Rational interface.
import org.qi4j.api.property.Property;
import org.qi4j.api.value.ValueComposite;
public interface Rational extends ValueComposite {
Property<Integer> nominator();
Property<Integer> denominator();
Rational add(Rational toAdd);
Rational mul(Rational toMultiply);
}
The highlighted lines show new behavior methods. If you try to run the application you will see the exception with the following message:
No implementation found for method public abstract com.blogspot.javasnippet.qi4j.Rational com.blogspot.javasnippet.qi4j.Rational.add(com.blogspot.javasnippet.qi4j.Rational) in com.blogspot.javasnippet.qi4j.Rational
That's because Qi4j doesn't see any implementation for these new methods.
Step 2. Implement mixin.
public interface Rational extends ValueComposite {
Property<Integer> nominator();
Property<Integer> denominator();
Rational add(Rational toAdd);
Rational mul(Rational toMultiply);
abstract class RationalMixin implements Rational {
@Override
public Rational add(Rational toAdd) {
return null;
}
@Override
public Rational mul(Rational toMultiply) {
return null;
}
}
}
Things worth to be noted:
- RationalMixin class is defined inside Rational interface - although it's not mandatory and is done here for simplicity there are cases (mainly for modifiers) where it makes sense to put implementation directly into declaring interface.
- RationalMixin class is abstract - as I want to implement only behavior methods I have to make this class abstract. This way it's possible to even split single interface implementation into set of mixins. Qi4j proxies and reflection does the rest.
Qi4j still is not aware of this stub implementation and crashes by throwing this same exception as before.
Step 3. Register mixin.
To register mixin I have to annotate interface Rational with @Mixins annotation like below.
@Mixins(Rational.RationalMixin.class)
public interface Rational extends ValueComposite {
...
}
Now the application is running fine - Qi4j sees implementation for all declared methods.
Step 4. Accessing composite state.
UPDATE: It's no longer necessary to inject state to the mixin. See p.4.1
Rational extends ValueComposite which in turn extends Composite interface. If you look at source code of Composite interface then you will see it declares usage of PropertyMixin - that's the one which is responsible for properties state management. So our Rational is composed out of two fragments: PropertyMixin and RationalMixin. To implement methods inside the RationalMixin we need to access the state of this Rational composite instance. Qi4j solves this by dependency injection. See highlighted lines below.
abstract class RationalMixin implements Rational {
@This
private Rational state;
@Override
public Rational add(Rational toAdd) {
return null;
}
@Override
public Rational mul(Rational toMultiply) {
return null;
}
}
According to the JavaDoc, annotation @This
"denotes the injection of a reference to the same Composite as the fragment is a part of". Then the method add() can be implemented as:
public Rational add(Rational toAdd) {
int n1 = state.nominator().get();
int d1 = state.denominator().get();
int n2 = toAdd.nominator().get();
int d2 = toAdd.denominator().get();
int d = d1 * d2;
int n = n1 * d2 + n2 * d1;
return null;
}
Step 4.1. Accessing composite state without using @This.
I wasn't aware of this feature when I was writing this post. Here is an update.
Qi4j routes calls to the abstract methods (in other words - the methods which are not implemented by the mixin) to the composite. So the implementation of RationalMixin can be rewritten as follows:
abstract class RationalMixin implements Rational {
@Override
public Rational add(Rational toAdd) {
int n1 = this.nominator().get();
int d1 = this.denominator().get();
int n2 = toAdd.nominator().get();
int d2 = toAdd.denominator().get();
int d = d1 * d2;
int n = n1 * d2 + n2 * d1;
return null;
}
@Override
public Rational mul(Rational toMultiply) {
return null;
}
}
Step 5. Accessing module resources.
Now it's time to create new Rational instance inside RationalMixin. To do this we need reference to the ValueBuilderFactory. This is solved by the dependency injection as well. Qi4j defines annotation @Structure which
"denotes the injection of a resource specific for the module which the injected object/fragment is instantiated in".
abstract class RationalMixin implements Rational {
@Structure
private ValueBuilderFactory builderFactory;
@Override
public Rational add(Rational toAdd) {
int n1 = this.nominator().get();
int d1 = this.denominator().get();
int n2 = toAdd.nominator().get();
int d2 = toAdd.denominator().get();
int d = d1 * d2;
int n = n1 * d2 + n2 * d1;
ValueBuilder<Rational> builder = builderFactory.newValueBuilder(Rational.class);
builder.prototype().nominator().set(n);
builder.prototype().denominator().set(d);
return builder.newInstance();
}
@Override
public Rational mul(Rational toMultiply) {
return null;
}
}
The implementation of method add() is now finished. We can check if it works. Here is new body of method main() which computes 2/3 + 3/4 and prints the result to the console.
public static void main(final String[] args) throws Exception {
SingletonAssembler assembler = new SingletonAssembler() {
@Override
public void assemble(ModuleAssembly module) throws AssemblyException {
module.addValues(Rational.class);
}
};
ApplicationSPI application = assembler.application();
Module module = assembler.module();
ValueBuilder<Rational> rb;
rb = module.valueBuilderFactory().newValueBuilder(Rational.class);
rb.prototype().nominator().set(2);
rb.prototype().denominator().set(3);
Rational r1 = rb.newInstance();
rb = module.valueBuilderFactory().newValueBuilder(Rational.class);
rb.prototype().nominator().set(3);
rb.prototype().denominator().set(4);
Rational r2 = rb.newInstance();
Rational r3 = r1.add(r2);
System.out.println(r3.nominator().get() + "/" + r3.denominator().get());
}
You should see:
17/12
Summary
This is last post of my short series about Qi4j basics. Here is short list of things worth to be remembered:
- Qi4j is composite oriented and manages composites rather than objects.
- Qi4j aims to help us in easier DDD adoption.
- Qi4j uses dependency injection.
Qi4j offers much more features than I know about (I'm still learning). If you want to learn more go to its
website. I encourage you to give it a try.