Pretty image
In the latest installment of his series on the Scala language, Venkat shows the power of chaining traits.

We discussed traits in the previous article of this series. In this article we take that discussion further to show how traits can chain. Unlike the method collision worries in multiple inheritance, methods of traits can cooperate to elegantly implement the decorator pattern.

Let’s look at our first assignment....

We’ve been tasked by a large company to check records of candidates for employment. We’re told that there may be a number of criteria to check before a person can be hired. Let’s design a mechanism for checking in such a way that new criteria can be easily added and the combination of criteria can be easily changed.

Let’s first start with the Person class.

 case class Person(first: String, last: String) {
  override def toString = first + " " + last
 }

Since there are different criteria, let’s create an abstract base class for them to conform to.

 abstract class Checker {
  def check(applicant : Person) : Boolean
 }

The check method in the implementation classes will tell us whether a given applicant qualifies by returning a boolean result.

We will use this Checker in an evaluateApplicant function.

 def evaluateApplicant(applicant : Person, checker: Checker) = {
  println("Received application for " + applicant)
  val result = if(checker.check(applicant)) "approved" else "disapproved"
  println("Application " + result)
 }

The evaluateApplicant method receives an applicant and an instance of Checker. It prints details of the applicant and then utilizes the services of the given Checker to decide if the applicant qualifies.

The company tells us we need to verify the education records of any candidate. The EducationChecker class will take care of that for us. It extends the Checker and overrides the check method.

 class EducationChecker extends Checker {
  override def check(applicant : Person) = {
  println("checking education...")
  //...
  applicant.last.length > 1
  }
 }

The check method of EducationChecker will evaluate the education records of the given applicant by communicating with appropriate services that can provide necessary details. Here we will simply fake a solution that returns true if the person’s last name has more than one letter. Let’s use the above code on an applicant with a single-letter last name.

 val john = Person("John", "X")
 evaluateApplicant(john, new EducationChecker)
 //Received application for John X
 //checking education...
 //Application disapproved

The education checker was invoked and it determined that the candidate is not qualified.

In addition to the education being checked for all candidates, the company wants the credit scores of some candidates (maybe those who will deal heavily with finances) to be checked.

We don’t want to add a credit check to the EducationChecker as it does not belong there. Furthermore, not all candidates need that check, so we need a way to mix the education check with credit check when we desire. In order to facilitate that, we will create a trait that will allow such mixing.

 trait CreditChecker extends Checker {
  abstract override def check(applicant : Person) = {
  println("checking credit...")
  //...
  super.check(applicant) && (applicant.last.length > 1)
  }
 }

The CreditChecker trait extends the Checker abstract base class. This trait can be mixed into any class that extends from the Checker. When mixed in, this trait will form a chain to connect to the class it is mixed into. If multiple traits are mixed in, they form a chain in the order in which they’re mixed in. For example if two traits are mixed into a class, then the chain will contain an instance of the class, followed by an instance of the first trait mixed in, and then an instance of the second trait.

We need to override the check method of Checker in the CreditChecker trait. At the same time, we need the destination class, the class into which this trait will be mixed in, to provide the check method. We indicate this with a rather odd combination of keywords abstract override.

In the check method of CreditChecker, we evaluate the credit and combine that result with the result of the previous checker in the chain using the call to super.check(...).

Let’s use the CreditChecker in combination with the EducationChecker.

 evaluateApplicant(john, new EducationChecker with CreditChecker)
 //Received application for John X
 //checking credit...
 //checking education...
 //Application disapproved

To use only the EducationChecker we can send an instance of that class to the evaluateApplicant function. If we want to add a check for credit, we can simply mix the CreditChecker trait in with the EducationChecker at the time of the call to evaluateApplicant.

The mixing of traits went fairly well. Now, we hear from the company that for some candidates we need to check education and criminal records. To accommodate this requirement let’s first create a CrimeChecker trait.

 trait CrimeChecker extends Checker {
  abstract override def check(applicant : Person) = {
  println("checking criminal records...")
  //...
  super.check(applicant) && (applicant.last.length > 1)
  }
 }

Let’s use this trait along with the EducationChecker.

 evaluateApplicant(john, new EducationChecker with CrimeChecker)
 //Received application for John X
 //checking criminal records...
 //checking education...
 //Application disapproved

Our design allows us to choose an EducationChecker and, in addition, optionally add another checker. Our design is quite extensible to accommodate more than one additional check. For instance, if the company wants us to apply all the above three checks, we can do that without writing any additional classes. Let’s try that on another candidate.

 evaluateApplicant(Person("Mark", "Who"), new EducationChecker
  with CrimeChecker with CreditChecker)
 //Received application for Mark Who
 //checking credit...
 //checking criminal records...
 //checking education...
 //Application approved

We see how the traits collaborate with each other rather than collide. The order in which the methods are evaluated is from right to left, the reverse order of how the traits are mixed in.

When we design with traits, we can avoid class proliferation and mix in the appropriate traits to create a flavor or combination of features. The ability to mixin traits on instances provides the added advantage that we don't have to determine these combinations ahead of time.

Even though Scala allows mixing traits on instances, it does not compromise compile-time checking. Traits are entirely handled at compile time and this does not involve any runtime surprises.

Congratulations! In this article you learned how traits can form a chain and collaborate to implement a combination of behaviors. You can now mix this into your set of available design tools.

Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., and an adjunct faculty at the University of Houston.

He has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with agile practices on their software projects.

Venkat is the author of .NET Gotchas, the coauthor of 2007 Jolt Productivity Award winning Practices of an Agile Developer, the author of Programming Groovy: Dynamic Productivity for the Java Developer and Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine. His latest book is Programming Concurrency on the JVM: Mastering Synchronization, STM, and Actors.

This series started in the September 2011 issue and has been running continuously since then. If you’d like to read the whole series, here are the links to the articles published so far:

  1. 9/11: The Elegance of Scala

  2. 10/11: Sensible Typing and Optional Items

  3. 11/11: Cute Classes and Pure OO

  4. 12/11: Functional Style of Programming

  5. 1/12: Working with Collections

  6. 2/12: Creating Higher Order Functions

  7. 3/12: Pattern Matching

  8. 4/12: XML as First Class Citizen

  9. 5/12: Recursions and Tail Call Optimization

  10. 6/12: Using Traits

  11. 7/12: Chaining Traits

Send the author your feedback or discuss the article in the magazine forum.