33

I have read that with Scala, it is generally advised to use Traits instead of Abstract classes to extend a base class.

Is the following a good design pattern and layout? Is this how Traits were intended to replace Abstract?

  • client class (with def function1)
  • trait1 class (overrides function1)
  • trait2 class (overrides function1)
  • specificClient1 extends client with trait1
  • specificClient2 extends client with trait2
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
kliew
  • 3,073
  • 1
  • 14
  • 25

2 Answers2

68

I don't know what your source is for the claim that you should prefer traits over abstract classes in Scala, but there are several reasons not to:

  1. Traits complicate Java compatibility. If you have a trait with a companion object, calling methods on the companion object from Java requires bizarre MyType$.MODULE$.myMethod syntax. This isn't the case for abstract classes with companion objects, which are implemented on the JVM as a single class with static and instance methods. Implementing a Scala trait with concrete methods in Java is even more unpleasant.
  2. Adding a method with an implementation to a trait breaks binary compatibility in a way that adding concrete methods to a class doesn't.
  3. Traits result in more bytecode and some additional overhead related to the use of forwarder methods.
  4. Traits are more powerful, which is bad—in general you want to use the least powerful abstraction that gets the job done. If you don't need the kind of multiple inheritance they support (and very often you don't), it's better not to have access to it.

The last reason is by far the most important in my view. At least a couple of the other issues might get fixed in future versions of Scala, but it will remain the case that defaulting to classes will constrain your programs in ways that are (at least arguably) consistent with good design. If you decide you actually really do want the power provided by traits, they'll still be there, but that'll be a decision you make, not something you just slip into.

So no, in the absence of other information, I'd suggest using an abstract class (ideally a sealed one) and two concrete classes that provide implementations.

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • 2
    As a footnote, Scala's approach to linearization _is_ a clever solution to the "problem" of multiple inheritance, and when you need it it works quite well (with the exception of a few things like fields). But you should never use something just because it's clever. – Travis Brown Feb 07 '16 at 09:20
  • 1
    Thanks, that answers my question. My source was https://www.safaribooksonline.com/library/view/scala-cookbook/9781449340292/ch04s13.html. Your answer makes more sense though – kliew Feb 07 '16 at 09:51
  • 2
    With Scala [2.12](http://www.scala-lang.org/news/2.12.0) do #1-3 still apply vis-a-vis `"A trait compiles directly to an interface with default methods. This improves binary compatibility and Java interoperability."`? Of course #4 is reason enough to choose an `abstract class` over a `trait`, I believe. @TravisBrown – Kevin Meredith May 23 '17 at 22:02
  • Travis, do you have any pointers for traits being more powerful? – laylaylom Jan 25 '18 at 18:01
2

OTOH, traits allow you to build and test the functionality of complex objects in a granular fashion, and to reuse core logic so as to provide different flavors. For example, a domain object might be deployed to a data server, which persists to a database, while a web server might employ read-only versions of the same object that are updated from the data server.

Nothing is suitable for every scenario. Use the right construct for the task at hand. Sometimes the reality of an implementation brings to light issues for specific use cases which were unknown at design time. Re-implementing using different assumptions and constructs can yield surprising results.

Mike Slinn
  • 7,705
  • 5
  • 51
  • 85