When designing a Scala library, you can partition the services offered by the library into traits. This gives your users more flexibility: they can mix into each class only the services they need from your library.
For example, in ScalaTest you create a basic suite of tests by mixing in any of several core Suite
traits:
class MySuite extends Suite // a basic suite of test methods class MySuite extends FunSuite // a basic suite of test functions class MySpec extends Spec // a BDD-style suite of tests class MyeSpec extends FeatureSpec // a higher level BDD-style suite class MySuite extends FixtureSuite // a suite of tests that take a fixture parameter
You can also mix in any of several other traits that add additional, or modify existing, behavior of the core trait:
class MySuite extends FunSuite with ShouldMatchers with EasyMockSugar with PrivateMethodTester
Although splitting a library's behavior into composable parts with traits gives users flexibility, one downside is that users may end up repeatedly mixing together the same traits, resulting in code duplication. User can easily eliminate this duplication, however, by creating a convenience trait that mixes together the behavior they prefer, and then mixing in that convenience trait into their classes instead. For example:
trait ProjectSpec extends WordSpec with ShouldMatchers with EasyMockSugar
class OneSpec extends ProjectSpec class TwoSpec extends ProjectSpec class RedSpec extends ProjectSpec with PrivateMethodTester class BlueSpec extends ProjectSpec
Besides just mixing together the things they need, users can add value inside their convenience traits. For example, projects often require many tests that share a common fixture, such as a connection to a database containing a clean set of test data. This could be addressed simply by creating yet another trait that mixes in ProjectSpec
and adds the needed pre- and post-test behavior:
trait DBProjectSpec extends ProjectSpec with BeforeAndAfterEach { override def beforeEach() { // initialize the database and open the connection } override def afterEach() { // clean up the database and close the connection } }
Now test classes that need a database can simply mix in DBProjectSpec
:
class MySpec extends DBProjectSpec
Although the ease of behavior composition afforded by traits is very useful, it has some downsides. One downside is that name conflicts are difficult to resolve. A user can't, for example, mix together two traits that contain methods with signatures that cause an overload conflict. Still another downside is that it is slightly awkward to experiment with the services offered by a trait in the Scala interpreter, because before the trait's services can be accessed, it must be mixed into some class or object. Both of these downsides can be addressed by making it easy to import the members of a trait as an alternative to mixing them in.
Scala has two features that make it easy to offer users this choice between mixins and imports. First, Scala allows users to import the members of any object. Second, Scala allows traits to have a companion object---a singleton object that has the same name as its companion trait. You implement the selfless trait pattern simply by providing a companion object for a trait that itself mixes in the trait. Here's a simple example of the selfless trait pattern:
trait Friendly { def greet() { println("hi there") } } object Friendly extends Friendly
Trait Friendly
in this example has one method, greet
. It also has a companion object, named Friendly
, which mixes in trait Friendly
. Given this friendly design, client programmers of this library can access the services of Friendly
either via mix in composition, like this (imports and uses of Friendly
are in bold):
object MixinExample extends Application with Friendly { greet() }
Or by importing the members of the Friendly
companion object, like this:
import Friendly._ object ImportExample extends Application { greet() }
Although the external behavior of MixinExample
is the same as ImportExample
, when MixinExample
invokes greet
it is calling greet
on itself (i.e., on this
), but when ImportExample
invokes greet
, it is calling greet
on the Friendly
singleton object. This is why being able to fall back on an import allows users resolve name conflicts. For example, a user would not be able to mix the following Functional
trait into the same class as Friendly
:
trait Functional { def greet: String = "hi there" }
Because Friendly
and Functional
's greet
methods have the same signature but different return types, they will not overload if mixed into the same class:
object Problem extends Application with Friendly with Functional // Won't compile
By contrast, the offending method can be renamed on import, like this:
import Friendly.{greet => sayHi} object Solved extends Application with Functional { sayHi() println(greet) }
A good real-world example is trait ShouldMatchers
from ScalaTest, which follows the selfless trait pattern. I expect the most common way ShouldMatchers
will be used is by mixing it into test classes, often by means of a convenience trait. Here's an example:
import org.scalatest.WordSpec import scala.collection.mutable.Stack import org.scalatest.matchers.ShouldMatchers class StackSpec extends WordSpec with ShouldMatchers { "A Stack" should { "pop values in last-in-first-out order" in { val stack = new Stack[Int] stack.push(1) stack.push(2) stack.pop() should be === 2 stack.pop() should be === 1 } "throw NoSuchElementException if an empty stack is popped" in { val emptyStack = new Stack[String] evaluating { emptyStack.pop() } should produce [NoSuchElementException] } } }
Occasionally, however, users may want to experiment with the ShouldMatchers
syntax in the Scala interpreter, which they can do with an import:
scala> import org.scalatest.matchers.ShouldMatchers._ import org.scalatest.matchers.ShouldMatchers._ scala> Map("hi" -> "there") should (contain key ("hi") and not contain value ("dude")) scala> List(1, 2, 3) should have length 2 org.scalatest.TestFailedException: List(1, 2, 3) did not have length 2 at org.scalatest.matchers.Matchers$class.newTestFailedException(Matchers.scala:148) at org.scalatest.matchers.ShouldMatchers$.newTestFailedException(ShouldMatchers.scala:2318) at org.scalatest.matchers.Matchers$ResultOfHaveWordForSeq.length(Matchers.scala:2891) at .<init>(<console>:7) at .<clinit>(<console>) ...
The reason this is called the selfless trait pattern is that it generally only makes sense to do this with traits that don't declare a self type. (A self type is a more specific type for this
that restricts what the trait can be mixed into.) One other way in which traits that follow this pattern are "selfless" is that rather than forcing users to mix in their services they instead give users a choice: either mixing in the trait or importing the members of its companion object. If you are designing a Scala library that has a trait that doesn't declare a self type, consider implementing the Selfless Trait pattern by creating a companion object that mixes in the trait.
The ScalaTest examples shown in this article are taken from ScalaTest 1.0:
http://www.artima.com/scalatest/#snapshot
Have an opinion? Readers have already posted 4 comments about this article. Why not add yours?
Bill Venners is president of Artima, Inc., publisher of the Artima Developer website (www.artima.com). He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Active in the Jini Community since its inception, Bill led the Jini Community's ServiceUI project, whose ServiceUI API became the de facto standard way to associate user interfaces to Jini services. Bill is also the lead developer and designer of ScalaTest, an open source testing tool for Scala and Java developers.
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.