Skip to content

Repository files navigation

Build Status

JComposition

JComposition is a lightweight Java API based on annotations for creating compositions at compile-time. Composition over inheritance.

Introduction

Say you have 2 interfaces:

publicinterfaceIDrawable {
voiddraw();
}
publicinterfaceIUpdatable {
voidupdate();
}

And 2 classes that implements them:

publicclassDrawableimplementsIDrawable {
@Overridepublicvoiddraw() {
System.out.println("I'm drawing");
}
}
publicclassUpdatableimplementsIUpdatable {
@Overridepublicvoidupdate() {
System.out.println("I'm updating");
}
}

And perhaps you want a GameObject class that have Drawable and Updatable behaviour:

publicclassGameObjectextendsDrawable, Updatable {
}
GameObjectgameObject = newGameObject();
gameObject.update();
gameObject.draw();

But Java does not allow you to have more than one superclass. So the code above will not compile.

JComposition is tool that you can use to mix such logic in one class without duplicated code. Also it support dependency injection and generics.

Using JComposition

JComposition uses interfaces as base for code generation.

Basic usage

Define your interfaces for each module you want to extend:

@Bind(Drawable.class)
publicinterfaceIDrawable {
voiddraw();
}
@Bind(Updatable.class)
publicinterfaceIUpdatable {
voidupdate();
}

@Bind annotation binds your interface to class that implements logic. Implementation of IDrawable and IUpdatable you can find in Introduction section

Then create a interface for GameObject class:

@Composition(name = "GameObjectBase")
@Bind(GameObject.class)
publicinterfaceIGameObjectextendsIUpdatable, IDrawable {
}

@Composition annotation marks this interface for annotation processor to generate composite class with name = "GameObjectBase".

Then just extend GameObject from GameObjectBase:

publicclassGameObjectextendsGameObjectBase {
}
// Now this will workGameObjectgameObject = newGameObject();
gameObject.update();
gameObject.draw();

Overriding

By default gameObject.draw() will call getComposition().composition_Drawable.draw(), but you can override behaviour this way:

publicclassGameObjectextendsGameObjectBase {
@Overridepublicvoiddraw() {
super.draw(); // will call getComposition().composition_Drawable.draw()// Some custom actiongetComposition().composition_Updatable.update();
}
}
gameObject.draw();
// OutputI'm drawing
I'm updating

Dependency injection

Use @UseInjection annotation to let processor mark composition's fields @Inject annotation.

@Bind(Movable.class)
@UseInjectionpublicinterfaceIMovable {
booleanmoveTo(intx, inty);
}
publicclassMovableimplementsIMovable {
@OverridepublicbooleanmoveTo(intx, inty) {
System.out.println("I'm moving to (" + x + ", " + y + ")");
returnfalse;
}
protectedabstractvoidonMove();
}
// And finally @Module declaration@ModulepublicfinalclassMovableModule {
privateGameObjectWithInjection.Compositioncomposition;
publicMovableModule(GameObjectWithInjection.Compositioncomposition) {
this.composition = composition;
}
@ProvidespublicGameObjectWithInjectionBase.Composition.Composition_MovableprovideMovable() {
returncomposition.newComposition_Movable();
}
}

When Movable composition will ready for injection abstract method onInject(Composition) will be called:

publicclassGameObjectWithInjectionextendsGameObjectWithInjectionBase {
privateInjectionComponentinjectionComponent;
@OverrideprotectedvoidonMove() {
System.out.println("OnMove()");
}
@OverrideprotectedvoidonInject(Compositioncomposition) {
injectionComponent = DaggerInjectionComponent
.builder()
.movableModule(newMovableModule(composition))
.build();
injectionComponent.inject(composition);
}
}

Diamond Problem

JComposition allow you to inherit functionality from many instances, and you could get into a situation that two or more components of composition has some equal methods. Here and below I will call such situation as 'merge conflict'. To solve merge conflict you could use special option in annotation @CompositiononConflict, which accept one of classes below or you own, that implements IMergeConflictPolicy:

MakeAbstractPolicy.classMixVoidPolicy.classUseFirstPolicy.class

Constraints

If you are not using dependency injection, binded class must have an empty argument constructor.

Examples

You can find more examples here

Download

We are using JitPack for publishing our libraries. Add jitpack.io to your repositories first to build.gradle:

allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}

And add the dependency:

dependencies {
// Use compile for processor instead of apt if you haven't apt dependency.
apt 'com.github.trollsoftware.jcomposition:processor:1.2.1'
compile 'com.github.trollsoftware.jcomposition:api:1.2.1'
}

Ideas

  1. Custom constructor support
  2. Check how jcomposition works on java 8-9
  3. Inherit java docs in generated files
  4. Add more documentation and examples

License

This library is distributed under the Apache 2.0 license found in the LICENSE file.

About

Composition over inheritance. Lightweight Java/Android API for creating a compositions at compile-time

Topics

Resources

Stars

10 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages