4

I'm just wondering if it is semantically correct to use traits to build game objects. On one hand, I view this as a has a relationship (an object has components), but on the other hand I view components as composing an object.

For example. You have a GameObject. A GameObject does pretty much nothing on its own, but the things you mix into it give it additional properties. Components could be HealthComponent (has health), PhysicsComponent (simulates physics), ClickableComponent (can be clicked).

I like the idea of using traits because all of the properties and methods are added on the original object and I can do player.getHP instead of player.getHealthComponent.getHP. On the other hand, I find the naming and semantics of using traits weird. trait HealthComponent extends GameObject - this doesn't make sense. A HealthComponent belongs to a GameObject, it doesn't fulfill the is a relationship that's implied by extend. Am I correct in assuming that traits are normally treated as specialized versions of their parent class? If so, how would I name something like the above object?

ryeguy
  • 65,519
  • 58
  • 198
  • 260

3 Answers3

4

To supplement @Moritz's answer, it's also possible to stack related behaviors without inheriting implementation from the super type:

trait HasFoo { def foo: Unit }

class GameObject extends HasFoo {
   def foo = {}
}

trait Health extends HasFoo { 
   self: GameObject =>

   abstract override def foo = {
      println("health foo")
      super.foo
   }
}

trait Dog extends HasFoo { 
   self: GameObject =>

   abstract override def foo = {
      println("dog foo")
      super.foo
   }
}

scala> val g = new GameObject with Health with Dog
g: GameObject with Health with Dog = $anon$1@33b7b32c

scala> g.foo
dog foo
health foo
Community
  • 1
  • 1
Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
3

If you want to restrict the types your trait can be mixed in you can express the dependency as a self-type:

trait Health { self: GameObject =>
 // ...
}

class Player extends GameObject with Movement with Health

This way your trait does not extend GameObject but can only be used as a mixin for subtypes of GameObject.

See also Understanding Scala's Cake Pattern and the linked article.

Community
  • 1
  • 1
Moritz
  • 14,144
  • 2
  • 56
  • 55
  • That's better, thanks. But if both `Player` and `Movement` define method `foo`, how can I make it so both get called (something that works like `super`)? – ryeguy Apr 07 '11 at 01:08
  • 2
    You can't. You can override methods but you cannot call the super type's implementation. If you need to do that you have to use inheritance. – Moritz Apr 07 '11 at 01:23
  • 2
    You can call the super type's implementation without inheriting from the super type by using abstract overrides. Your traits must inherit from a common interface that declares the `foo` method, though. I'll demonstrate this in an answer. – Aaron Novstrup Apr 07 '11 at 16:28
3

"trait HealthComponent extends GameObject- this doesn't make sense. AHealthComponent` belongs to a GameObject" -- what you want is self types, and building according to the cake pattern:

So you have

trait HealthComponent {
   me: GameObject =>

   def inspect { me.querySomeGameObjectProperty }
}

val x = new GameObject with HealthComponent with ...

"Am I correct in assuming that traits are normally treated as specialized versions of their parent class?" -- I wouldn't say so. If you go for a bottom-up approach like the above, you don't think so much as traits being subtypes of some parents, rather the other way round (larger components being composed of and specialised by traits)

Community
  • 1
  • 1
0__
  • 66,707
  • 21
  • 171
  • 266