Skip to content

Repository files navigation

dependency

Maven Central

Legacy README

This library defines a series of interfaces that regulate cases of looking up global resources dynamically and handling weak coupling components.

Concepts

  • Component is what can be found in scope, despite it is stateful or has dependencies.
  • Dependent is a type of Component. It wants to find other components in the scope. When a new component is set up to the scope, dependents' handle will be called to get the information of this component, and the dependent may save the reference of that component.
  • DynamicScope is the scope we talked in the two previous concepts. It resolves the dependency relationship between components.

Example

There is an example that shows how this library can be used to build a robot hierarchy.

Imagine that we have a such definition of motors:

classMotor(name:String, inverse:Boolean) : NamedComponent<Motor>(name) {
funsetPower(power:Double): Unit= {
// ...
}
}

NamedComponent is a type of Component which has a name. We can find it by name in the scope. A motor can be set a power to run on. It also has a name and inverse which indicates that if the direction of the motor should be inverted. This depends on how the motor was installed in real life.

Next, we have the definition encoders:

classEncoder(name:String, inverse:Boolean) : NamedComponent<Encoder>(name) {
fungetPosition(): Double {
//...
}
fungetSpeed(): Double {
//...
}
}

We can get the position and speed of an encoder. Similar to motors, encoders may need to inverse as well. Motor and Encoder are real devices installed on our robot. In order to implement a feedback control, we can assemble them together in a new structure:

classMotorWithEncoder(name:String) : Dependent,
NamedComponent<MotorWithEncoder>(name), ManagedHandler by managedHandler() {
privateval pid =PID(/* args */)
privateval motor:Motor by manager.must(name)
privateval encoder:Encoder by manager.must(name)
var targetPosition:Double= .0funrun() {
val delta = targetPosition - encoder.getPosition()
val output = pid.run(delta)
motor.setPower(output)
}
}

Dependent means this component depends on other components, and ManagedHandler by managedHandler() creates a delegate that handles the dependencies. This is the case that an encoder is installed with a motor, so we can know how much the motor run and implement PID control. Two motors can driver a simple chassis:

classChassis : Dependent, UniqueComponent<Chassis>(), ManagedHandler by managedHandler() {
privateval left:MotorWithEncoder by manager.must("left")
privateval right:MotorWithEncoder by manager.must("right")
funtranslateToPosition(position:Double) {
left.targetPosition = position
right.targetPosition = position
}
}

UniqueComponent indicates that Chassis is a unique component in the scope. Also, it is a Dependent, where we use manager.must to find MotorWithEncoder in scope. In addition, We have a distance sensor:

classDistanceSensor : UniqueComponent<DistanceSensor>() {
fungetDistanceToWall(): Double {
// ...
}
}

It is unique component, and can measure the distance to wall. A remote control can command our robot:

classRemoteControl : Dependent, UniqueComponent<RemoteControl>(),
ManagedHandler by managedHandler() {
privateval chassis:Chassis by manager.must()
privateval distanceSensor:DistanceSensor by manager.must()
funtranslateRobot(position:Double) {
if (distanceSensor.getDistanceToWall() > position) {
chassis.translateToPosition(position)
}
}
}

It depends on Chassis and DistanceSensor. Again, we use the same trick to handle dependencies.

Finally, our robot is basically a dynamic scope:

val robot = scope {
funsetupMotorWithEncoder(name:String, inverse:Boolean) {
setup(Motor(name, inverse))
setup(Encoder(name, inverse))
setup(MotorWithEncoder(name))
}
setupMotorWithEncoder("left", false)
setupMotorWithEncoder("right", true)
setup(DistanceSensor())
setup(RemoteControl())
}
val remoteControl = robot.components.must<RemoteControl>().translateRobot(x)

We set up everything to the DynamicScope, and the scope will help us deal with all dependencies. No references passed through constructors, nor worries about instantiation orders!

Manager style

We have seen how the manager was used to declare dependency. ManagedHandler by managedHandler() is trivial, and the following two code snippets are identical:

Manually:

classAAA : UniqueComponent<AAA>()
classBBB : Dependent, UniqueComponent<BBB>() {
val manager =DependencyManager()
val aaa:AAA by manager.must()
overridefunhandle(dependency:Component): Boolean= manager.handle(dependency)
}

managedHandler():

classAAA : UniqueComponent<AAA>()
classBBB : Dependent, UniqueComponent<BBB>(), ManagedHandler by managedHandler() {
val aaa:AAA by manager.must()
}

Annotation style

The library also provides an annotation style dependency injection:

classAAA : UniqueComponent<AAA>()
classCCC(name:String) : NamedComponent<CCC>(name)
classBBB : Dependent, UniqueComponent<BBB>() {
@Must
lateinitvar aaa:AAA
@Maybe
@Name("ccc1")
var ccc:CCC?=null
@Must
lateinitvar ccc2:CCCprivateval injector by annotatedInjector()
overridefunhandle(dependency:Component): Boolean= injector.handle(dependency)
}
scope {
setup(AAA())
setup(CCC("ccc1"))
setup(CCC("ccc2"))
setup(BBB())
}

We need use annotatedInjector() to create an injector for the dependent, and manually delegate hanlde method to the injector. @Must declares a strict dependency with the type of the field, and @Maybe declares a weak dependency. @Name can specify dependency's name. Field's name will be used if no @Name annotated.

About

A lightweight dependency injection library

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages