Scala traits remove duplication of code and provide a cleaner design.
Inheriting from a class or abstraction is common in OO programming, but Java restricts us to live with single implementation inheritance. Those of us who’ve tried multiple inheritance (MI) in languages like C++ know the consequences. MI is like a gorilla: it’s cute at first but you don’t bring it home. You have to deal with method collision and base instance duplication. While Java avoided these issues, simply prohibiting MI does not solve the real problem. In this tenth article of the series we’ll see how traits elegantly avoid the MI issues and provide an elegant modeling technique.
Our application that manages finance has gone through an initial audit and we’ve been asked to encrypt some data to increase security. The customer class we wrote,
needs a special encrypt method. As a first attempt, we add that method in.
Here we have added an encrypt method in the Customer class. For this example we merely reverse the output of the call to the toString() method.
We can now directly call the encrypt method on an instance of Customer.
Soon, however, we realize that there are other classes in the application, like Stock, that requires this treatment. We know that duplicating the encrypt method in this and other classes is an act of criminal negligence. One possibile approach is to create an abstract base class and move the encrypt method into it. Then the Customer class can extend from that class. Unfortunately we quickly realize this will not work since the Stock class already has a base class, and not all securities may need the encrypt method.
The Java way to solve this problem would be to create an interface. That still does not prevent the duplication, as each of the classes needs the implementation. We will have to resort to delegation to avoid duplication. In Java, we would create a class with a static method. In Scala, that would be a singleton object.
Then in the Customer class we can invoke the encrypt method of the Encryptor.
The Stock class would have to do the same.
The core logic for encrypt is encapsulated, but there’s a thin layer of duplication in the classes that use it. Let’s see how traits solve this problem.
Scala does not have interfaces; instead, it has traits. A Scala trait with only method declarations and no implementation translates to a mere interface at the bytecode level.
Let’s create a trait called Encrypt.
Compile this trait using scalac and then view the generated bytecode using javap.
Scala traits directly map to Java interfaces. If we place implementations in the trait, then the Scala compiler creates an interface and an abstract base class for us. Let’s implement the encrypt method in our trait.
Compile this modified version of the Encrypt trait and take a look at the bytecode generated.
You probably have a hunch where this is going. Rather than requiring us to create the delegation, the Scala compiler takes care of it for us.
Let’s see how we can use this with the Customer class.
We extended the Customer class from the Encrypt trait in much the same way we would have extended from an abstract base class. The encrypt is now automatically part of Customer instances.
Since Stock already extends from Security, Scala allows us to bring the methods of the trait into the Stock class using a special with keyword.
Now all instances of Stock also have the encrypt method. If we take a look at the bytecode generated for the Customer and Stock classes using the javap -c command, we will see the encrypt method added to each of these classes during compile time.
We can bring multiple traits into a class at the same time simply by using multiple withs in the class definition. In the next article in this series we’ll see how we can force method collisions between multiple traits and use it to our advantage.
Scala allows us to apply traits to instances also. Let’s look at a CheckingAccount that does not have the traits.
Since CheckingAccount does not have any encrypt() method or the Encrypt trait, we can’t call the encrypt method on the instance we created. But even though the class does not have it, we can add traits to specific instances at the time of their creation.
While, in general, instances of CheckingAccount don’t have the encrypt method, our secretAccount is special and can be encrypted.
Traits in Scala are handled entirely at compile time. When you use instance-level traits, the compiler creates an anonymous inner class for that instance.
In this article we saw how traits remove duplication of code and provide a cleaner design. In the next article we’ll see how method collisions are handled and how we can put this to a greater use.
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:
9/11: The Elegance of Scala
11/11: Cute Classes and Pure OO
1/12: Working with Collections
3/12: Pattern Matching
6/12: Using Traits