Kotlin 223 - Simplifying
GitHub Decentralized Repo
GitHub Centralized Repo
I really thought I was finished improving the centralized version, but these small changes will, I think, make things much simpler overall.
Our new scheme is to go back to interacting all collider pairs, without regard to type. We’ll sort out the types for objects a
and b
by doing this:
a.interactWith(b, trans)
b.interactWith(a, trans)
The interactWith
method will be implemented on all four collider classes, as
fun interactWith(other: Collider, trans: Transaction) {
other.interact(this, trans)
}
That will drop right down to the type-specific interact
functions that we already have. Double dispatch at its finest. Should be nothing to it.
Let’s just write a few tests for one object. That should drive out the pattern.
class InteractionDispatchTests {
@Test
fun `asteroid dispatch`() {
val asteroid = Asteroid(U.CENTER_OF_UNIVERSE)
val missile = Missile(Point(0.0, 0.0))
missile.position = U.CENTER_OF_UNIVERSE
val trans = Transaction()
asteroid.interactWithOther(missile, trans)
}
}
This should get me started. I’ll let IDEA help me create the method but I’ll fill it in.
fun interactWithOther(other: Collider, trans: Transaction) {
other.interact(this, trans)
}
This won’t compile, as I’ll need to adjust the interface. This will be part of the price we pay.
interface Collider {
val position: Point
val killRadius: Double
fun interact(asteroid: Asteroid, trans: Transaction)
}
That should get the test running. We have no assertions yet. Oops, now we have to implement the function in Asteroid. My bad.
override fun interact(asteroid: Asteroid, trans: Transaction) {}
Won’t everyone want override now? Yes, they all do.
Test. It runs. Did it work? We can check for split asteroids in the Transaction. But this will lead to trouble. First let’s get the test in place.
@Test
fun `asteroid dispatch`() {
val asteroid = Asteroid(U.CENTER_OF_UNIVERSE)
val missile = Missile(Point(0.0, 0.0))
missile.position = U.CENTER_OF_UNIVERSE
val trans = Transaction()
asteroid.interactWithOther(missile, trans)
assertThat(trans.removes).contains(missile)
}
OK, we have round trip. The scheme will work, I reckon. Let’s do the reverse test now, missile vs asteroid.
@Test
fun `asteroid dispatch`() {
val asteroid = Asteroid(U.CENTER_OF_UNIVERSE)
val missile = Missile(Point(0.0, 0.0))
missile.position = U.CENTER_OF_UNIVERSE
val removeMissile = Transaction()
asteroid.interactWithOther(missile, removeMissile)
assertThat(removeMissile.removes).contains(missile)
val addAsteroids = Transaction()
missile.interactWith(asteroid, addAsteroids)
assertThat(addAsteroids.asteroids.size).isEqualTo(2)
}
That demands the interactWith on missile, and a new entry in the Collider.
fun interactWith(other: Collider, trans: Transaction) {
other.interact(this, trans)
}
This will not compile because the only interact Collider knows so far is the asteroid one. We add:
interface Collider {
val position: Point
val killRadius: Double
fun interact(asteroid: Asteroid, trans: Transaction)
fun interact(missile: Missile, trans: Transaction)
}
This will require me to go through and add a raft of overrides. With those added, the test runs. The scheme works.
We are green. Commit: test of interactWith
for asteroid and missile works.
I think the thing will be to add the other cases to Collider and install all the overrides. And we should add interactWith
as well. But first the other interact
cases.
interface Collider {
val position: Point
val killRadius: Double
fun interact(asteroid: Asteroid, trans: Transaction)
fun interact(missile: Missile, trans: Transaction)
fun interact(saucer: Saucer, trans: Transaction)
fun interact(ship: Ship, trans: Transaction)
}
Testing will drive out all the compile errors and I can let IDEA paste in the override
.
I am green. Let’s arrange these methods nicely.
class Asteroid ...
override fun interact(asteroid: Asteroid, trans: Transaction) {}
override fun interact(missile: Missile, trans: Transaction) {
checkCollision(missile, trans)
}
override fun interact(saucer: Saucer, trans: Transaction) {
checkCollision(saucer, trans)
}
override fun interact(ship: Ship, trans: Transaction) {
checkCollision(ship, trans)
}
fun interactWithOther(other: Collider, trans: Transaction) {
other.interact(this, trans)
}
And so on. I’ll spare you the others. We are green. Commit: all four interact methods now in Collider and overridden in all four Collider classes.
Now if I add interactWith
to Collider, it’ll drive out the remaining two implementations.
interface Collider {
val position: Point
val killRadius: Double
fun interact(asteroid: Asteroid, trans: Transaction)
fun interact(missile: Missile, trans: Transaction)
fun interact(saucer: Saucer, trans: Transaction)
fun interact(ship: Ship, trans: Transaction)
fun interactWith(other: Collider, trans: Transaction)
}
Test to force the adds.
Sigh. I need to be consistent in my naming. I’ve used interactWith
and interactWithOther
. I’ll stick to interactWith
.
I have them all rigged up. Green. Commit: interactWith
implemented in all Collider implementors.
That should serve as a solid base. I’ll decide next time how many more tests to implement, and we’ll have some work to do on Interaction. I expect it to all go quite smoothly. Then we’ll have the tedium of removing all those separate collections that we worked so hard to build. The price of progress.
Summary
You could make a case that all that separation of types was wasted. It has only been in the system for a few days and now we’re taking it out. We “should” have seen this better way right from the get-go. Yes, well, we didn’t. Now we see a better way. And, especially because I’m here to explore better ways, we’ll do it.
In a real effort, with features needed, we might not be doing this centralized version at all. The decentralized works well and it has a certain niceness to it. Of course, we could do to it what we’re doing here. It’s just that we’d need 8 or 10 methods in every class instead of just the five.
I’ll be interested to see where this winds up. I am sure it’ll be smaller, and I think it’ll be simpler as well. We’ll find out!
See you next time!