This article shows how Scala adopts and transforms the classical software design patterns.

The content of the article is also (independently) translated into Russian and Chinese.

Design pattern is a general reusable solution to a commonly occurring problem in software design. A design pattern is not a finished code, but rather a template for how to solve a problem that can be used in many different situations.

Patterns are formalized best practices of effective design, which helps to prevent subtle issues, improve code readability and speed up the development process.

The classical design patterns (mostly, the GoF patterns) are object-oriented. They show relationships and interactions between classes and objects. These patterns are less applicable in pure functional programming (see Haskell’s Typeclassopedia and Scalaz for “functional” design patterns, kind of), however, since Scala is an object-functional hybrid language, these patterns are still very relevant, even in “functional” Scala code.

Sometimes, design patterns may be a sign of some missing feature in a programming language. In such cases, the patterns might be simplified or eliminated when a programming language provides the required features. In Scala, it’s possible to implement most classical design patterns directly, relying on the expressive language syntax.

While Scala may employ additional, language-specific design patterns, this article focuses on the well-known, classical patterns, because these patterns also serve as the means of communication between developers.

Sure we can use the Force all the power of Scala to completely transcend all these patterns, but in this article I intentionally tried to avoid more advanced techniques in favor of more simple and pictorial ones, in order to provide a clean mapping between languages.

Creational patterns:

Structural patterns:

Behavioral patterns:

So, here goes the patterns (all the code is available as a GitHub repository).



Factory method

The factory method pattern provides an interface for creating an object that encapsulates the actual class instantiation in a method, and lets subclasses decide which class to instantiate.

Factory method allows to:

merge complex object creation code,

select which class to instantiate,

cache objects,

coordinate access to shared resources.

We will consider the static factory method, which is slightly different from the classical version of the pattern – static factory method avoids subclassing, so there’s no option to override the method.

In Java, we use new operator to instantiate a class, by invoking its constructor. To implement the pattern, we rely on ordinary methods. Also, because we can’t define static methods in interface, we have to use a separate factory class:

public interface Animal {} private class Dog implements Animal {} private class Cat implements Animal {} public class AnimalFactory { public static Animal createAnimal(String kind) { if ("cat".equals(kind)) return new Cat(); if ("dog".equals(kind)) return new Dog(); throw new IllegalArgumentException(); } } AnimalFactory.createAnimal("dog");

In addition to constructors, Scala provides a special syntactic construct, that looks similar to constructor invocation, but it’s actually a convenient factory method:

trait Animal private class Dog extends Animal private class Cat extends Animal object Animal { def apply(kind: String) = kind match { case "dog" => new Dog() case "cat" => new Cat() } } Animal("dog")

The factory method is defined in so-called “companion object” – a special singleton object with the same name, defined in the same source file. This syntax is limited to the static version of the pattern, because we cannot delegate object creation to subclasses.

Pros: Reuses base class name.

Standard and concise.

Resembles constructor invocation. Cons: Static factory method.

Lazy initialization

Lazy initialization is a special case of lazy evaluation strategy. It’s a technique that initializes a value (or an object) on its first access.

Lazy initialization allows to defer (or avoid) some expensive computation.

A typical Java implementation uses null value to indicate uninitialized state. However, if null is a valid final value, then a separate flag is needed to indicate whether the initialization process has taken place.

In multi-threaded code, access to the flag must be synchronized to guard against a race condition. Efficient synchronization may employ double-checked locking, which complicates code even further:

private volatile Component component; public Component getComponent() { Component result = component; if (result == null) { synchronized(this) { result = component; if (result == null) { component = result = new Component(); } } } return result; }

Scala offers a clean built-in syntax to define lazy values:

lazy val x = { print("(computing x) ") 42 } print("x = ") println(x) // x = (computing x) 42

Lazy values in Scala can hold null values. Access to lazy value is thread-safe.

Pros: Concise syntax.

Lazy values can hold nulls.

Lazy values are thread-safe. Cons: Less control over initialization.

Singleton

The singleton pattern restricts the instantiation of a class to one object, and provides a global point of access to it.

Singleton is probably the most well-known design pattern in Java. It is a clear sign of the missing language feature.

While Java language includes static keyword, static members are not associated with any object, and static member classes cannot implement an interface. Thus, the concept of static methods in Java goes against the basic OOP premise that everything is an object. “Static members” is just a fancy new name for conventional subroutines.

So, when we need a globally accessible implementation of an interface (possibly, with lazy initialization), the Singleton pattern comes to the rescue:

public class Cat implements Runnable { private static final Cat instance = new Cat(); private Cat() {} public void run() { // do nothing } public static Cat getInstance() { return instance; } } Cat.getInstance().run()

However, more advanced Singleton implementations (with lazy initialization) can be rather verbose or prone to subtle errors (e.g. double-checked locking).

Scala provides concise direct realization of the singleton pattern in the language:

object Cat extends Runnable { def run() { // do nothing } } Cat.run()

Objects can inherit methods from classes or interfaces. Object can be referenced (directly or via an inherited interface). In Scala, objects are initialized on-demand.

Pros: Clear intent.

Concise syntax.

On-demand initialization.

Thread-safety. Cons: Less control over initialization.

Adapter

The adapter pattern converts interface of a class into expected interface, allowing classes with incompatible interfaces to work together.

Adapters are useful for integrating existing components.

Java implementation creates a wrapper class, that is explicitly used in the code:

public interface Log { void warning(String message); void error(String message); } public final class Logger { void log(Level level, String message) { /* ... */ } } public class LoggerToLogAdapter implements Log { private final Logger logger; public LoggerToLogAdapter(Logger logger) { this.logger = logger; } public void warning(String message) { logger.log(WARNING, message); } public void error(String message) { logger.log(ERROR, message); } } Log log = new LoggerToLogAdapter(new Logger());

In Scala, we have a built-in concept of interface adapters, expressed as implicit classes:

trait Log { def warning(message: String) def error(message: String) } final class Logger { def log(level: Level, message: String) { /* ... */ } } implicit class LoggerToLogAdapter(logger: Logger) extends Log { def warning(message: String) { logger.log(WARNING, message) } def error(message: String) { logger.log(ERROR, message) } } val log: Log = new Logger()

When expected type of expression is Log , yet a Logger instance is used, Scala compiler will automatically wrap that instance in the adapter class.

Another possible example of the Adapter pattern in Scala is typeclasses.

Pros: Clear intent.

Concise syntax. Cons: Might be obscure without IDE.

Decorator

The decorator pattern is used to extend functionality of some object, without affecting other instances of the same class. Decorators provide a flexible alternative to subclassing.

The decorator pattern is helpful when there are several independent ways of extending functionality, that can be arbitrarily combined together.

In Java, the goal is achieved by defining a new decorator class that inherits the base interface and wraps the original class, so that multiple decorators can be stacked on top of each other. An intermediate decorator class is often used to abstract the delegation of multiple methods:

public interface OutputStream { void write(byte b); void write(byte[] b); } public class FileOutputStream implements OutputStream { /* ... */ } public abstract class OutputStreamDecorator implements OutputStream { protected final OutputStream delegate; protected OutputStreamDecorator(OutputStream delegate) { this.delegate = delegate; } public void write(byte b) { delegate.write(b); } public void write(byte[] b) { delegate.write(b); } } public class BufferedOutputStream extends OutputStreamDecorator { public BufferedOutputStream(OutputStream delegate) { super(delegate); } public void write(byte b) { // ... delegate.write(buffer) } } new BufferedOutputStream(new FileOutputStream("foo.txt"))

To achieve the same goal, Scala provides a direct way of overriding interface methods, without binding to their concrete implementation in place of declaration. Here’s how we can use abstract override modifier:

trait OutputStream { def write(b: Byte) def write(b: Array[Byte]) } class FileOutputStream(path: String) extends OutputStream { /* ... */ } trait Buffering extends OutputStream { abstract override def write(b: Byte) { // ... super.write(buffer) } } new FileOutputStream("foo.txt") with Buffering // with Filtering, ...

The delegation is established statically, at compile time, but that’s usually enough as long as we can arbitrarily combine decorators in place of object creation.

As opposed to composition-based implementation, Scala’s approach preserves object identity, so we may safely use equals on decorated objects.

In Scala, such a use of traits is known as Stackable Trait Pattern.

Pros: Clear intent.

Concise syntax.

Object identity is preserved.

No explicit delegation.

No intermediate decorator class. Cons: Static binding.

No constructor parameters.

Value object

Value object is a small immutable object that represents a simple entity whose equality isn’t based on identity. Value objects are equal if all their fields are equal.

Value objects are widely used to represent numbers, dates, colors, etc. In enterprise applications they are used as DTOs a for inter-process communication. Because of the immutability, value objects are handy in multi-threaded programing.

in Java, there’s no special syntax for value objects, so we have to define (and update) a constructor, getters and auxiliary methods explicitly:

public class Point { private final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public boolean equals(Object o) { // ... return x == that.x && y == that.y; } public int hashCode() { return 31 * x + y; } public String toString() { return String.format("Point(%d, %d)", x, y); } } Point point = new Point(1, 2)

In Scala, we can use either tuples or case classes to declare value objects. When a separate class is not needed, tuples is the way to go:

val point = (1, 2) // new Tuple2(1, 2)

Tuples are predefined immutable “collections” that can hold a fixed number of items of different types. Tuples provide constructor, getters, and all the auxiliary methods.

We may also create a type alias for our point representation, so we may use it in declarations:

type Point = (Int, Int) // Tuple2[Int, Int] val point: Point = (1, 2)

When a dedicated class is required, or when we want more descriptive names for the data items, we may define a case class:

case class Point(x: Int, y: Int) val point = Point(1, 2)

Case classes export constructor parameters as properties. By default, case classes are immutable. Like tuples, they provide all the required methods automatically. Because case classes are valid classes, the can use inheritance and define members.

Value object pattern is ubiquitous tool in functional programming (as a part of ADT concept), and Scala offers complete support for value objects directly in the language.

Pros: Concise syntax.

Predefined tuple classes.

Built-in auxiliary methods. Cons: None.

Null Object

Null Object represents the absence of an object by defining a neutral, “do nothing” behavior.

This approach has an advantage over using null references, because there’s no need to explicitly check the validity of references before usage.

In Java we may implement the pattern by defining a special subclass with empty methods:

public interface Sound { void play(); } public class Music implements Sound { public void play() { /* ... */ } } public class NullSound implements Sound { public void play() {} } public class SoundSource { public static Sound getSound() { return available ? music : new NullSound(); } } SoundSource.getSound().play();

So, we don’t need to check the reference returned form getSound method before calling play . Additionally, we may define the Null Object as Singleton to limit the number of “null-instances” to one.

Scala uses a similar approach, yet it provides a predefined Option type, which can be used as a placeholder for optional value:

trait Sound { def play() } class Music extends Sound { def play() { /* ... */ } } object SoundSource { def getSound: Option[Sound] = if (available) Some(music) else None } for (sound <- SoundSource.getSound) { sound.play() }

In this case, we use so-called “for comprehension” to process the optional value (higher-order functions and pattern matching are also suitable for that purpose).

Pros: Predefined type.

Explicit optionality.

Works with built-in constructs. Cons: More verbose usage.

Strategy

The strategy pattern defines a family of encapsulated algorithms and lets the algorithm vary independently from clients that use it.

The pattern is handy when we need to select an algorithm at runtime.

In Java, strategy is usually implemented by creating a hierarchy of classes that inherit a base interface:

public interface Strategy { int compute(int a, int b); } public class Add implements Strategy { public int compute(int a, int b) { return a + b; } } public class Multiply implements Strategy { public int compute(int a, int b) { return a * b; } } public class Context { private final Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void use(int a, int b) { strategy.compute(a, b); } } new Context(new Multiply()).use(2, 3);

Because Scala offers first-class functions, we can use them to express the same concept directly:

type Strategy = (Int, Int) => Int class Context(computer: Strategy) { def use(a: Int, b: Int) { computer(a, b) } } val add: Strategy = _ + _ val multiply: Strategy = _ * _ new Context(multiply).use(2, 3)

When strategy consists of multiple methods, we may use a case class or a tuple to group these methods together.

Pros: Concise syntax. Cons: General-purpose type.

Command

The command pattern is used to encapsulate all the information needed to call a method at a latter time. This information includes the method name, the object that owns the method and values for the method parameters.

The command is useful to delay, sequence or log method calls.

In Java, the goal is achieved by wrapping the call in an object:

public class PrintCommand implements Runnable { private final String s; PrintCommand(String s) { this.s = s; } public void run() { System.out.println(s); } } public class Invoker { private final List<Runnable> history = new ArrayList<>(); void invoke(Runnable command) { command.run(); history.add(command); } } Invoker invoker = new Invoker(); invoker.invoke(new PrintCommand("foo")); invoker.invoke(new PrintCommand("bar"));

In Scala, we can rely on by-name parameter to defer evaluation of any expression:

object Invoker { private var history: Seq[() => Unit] = Seq.empty def invoke(command: => Unit) { // by-name parameter command history :+= command _ } } Invoker.invoke(println("foo")) Invoker.invoke { println("bar 1") println("bar 2") }

That is how we can convert arbitrary expression or code block to a function object. The calls to the println method are evaluated withing the calls to invoke method, and then stored as functions in the history sequence.

We can also define functions directly, without resorting to by-name parameters, however that approach is a little bit more verbose.

Pros: Concise syntax. Cons: General-purpose type.

Chain of responsibility

The chain of responsibility pattern decouples the sender of a request from its receiver, by giving more than one object an opportunity to handle the request. The request is processed by the chain until some object handles it.

In a typical implementation of the pattern every object in the chain inherits a base interface and contains an optional reference to a next processing object in the chain. Each object is given a chance to either handle the request (and interrupt the processing), or pass the request to the next handler in the chain. The sequencing logic can be either delegated to objects, or encapsulated in a base class:

public abstract class EventHandler { private EventHandler next; void setNext(EventHandler handler) { next = handler; } public void handle(Event event) { if (canHandle(event)) doHandle(event); else if (next != null) next.handle(event); } abstract protected boolean canHandle(Event event); abstract protected void doHandle(Event event); } public class KeyboardHandler extends EventHandler { // MouseHandler... protected boolean canHandle(Event event) { return "keyboard".equals(event.getSource()); } protected void doHandle(Event event) { /* ... */ } } KeyboardHandler handler = new KeyboardHandler(); handler.setNext(new MouseHandler());

Because such an implementation is somewhat similar to the Decorator‘s one, we may use abstract override functionality to achieve the goal. However, Scala provides an even more straightforward approach, that is based on partial functions.

A partial function is a function that is defined only for a subset of the possible values of its arguments. While we may call isDefinedAt and apply on partial functions directly in order to implement the sequencing, the better way is to rely on the built-in orElse method for chaining:

case class Event(source: String) type EventHandler = PartialFunction[Event, Unit] val defaultHandler: EventHandler = PartialFunction(_ => ()) val keyboardHandler: EventHandler = { case Event("keyboard") => /* ... */ } def mouseHandler(delay: Int): EventHandler = { case Event("mouse") => /* ... */ } keyboardHandler.orElse(mouseHandler(100)).orElse(defaultHandler)

Note that we need to use the defaultHandler to avoid errors on “undefined” events.

Pros: Concise syntax.

Built-in logic. Cons: General-purpose type.

Dependency injection

The dependency injection (DI) pattern allows to avoid hard-coded dependencies and to substitute dependencies either at run-time or at compile time. The pattern is a special case of inversion of control (IoC) technique.

Dependency injection is used to choose among multiple implementations of a particular component in application, or to provide mock component implementations for unit testing.

Apart from IoC containers, the simplest Java implementation uses constructor arguments to pass required dependencies. So, we use composition to express dependency requirements:

public interface Repository { void save(User user); } public class DatabaseRepository implements Repository { /* ... */ } public class UserService { private final Repository repository; UserService(Repository repository) { this.repository = repository; } void create(User user) { // ... repository.save(user); } } new UserService(new DatabaseRepository());

In addition to composition (“HAS-A”) and inheritance (“IS-A”), Scala offers a special kind of object relation – requirement (“REQUIRES-A”), expressed as self-type annotations. Self-types let us specify additional type expectations for an object, without exposing them in the inheritance hierarchy.

We can use self-type annotations together with traits to implement dependency injection:

trait Repository { def save(user: User) } trait DatabaseRepository extends Repository { /* ... */ } trait UserService { self: Repository => // requires Repository def create(user: User) { // ... save(user) } } new UserService with DatabaseRepository

Unlike constructor injection, this approach requires a single reference for each kind of dependency in configuration. The full implementation of this technique is known as Cake pattern (and there are many other ways to implement DI in Scala).

Since mixing in traits is done statically in Scala, this approach is limited to compile-time dependency injection. However, in practice, run-time re-configuration is rarely needed, while static checking of configuration is a definite advantage (over an XML-based configuration).

Pros: Clear intent.

Concise syntax.

Static checking. Cons: Compile-time configuration.

Might be verbose.

Afterword

I hope that this demonstration helps to bridge the gap between the two languages, so that Java programmers can easily gain insight into Scala syntax, and Scala programmers can map the language-specific constructs into the well-known abstractions.

See also:

Tags: adapter, chain of responsibility, command, decorator, dependency injection, design, factory method, lazy initialization, null object, pattern, scala, strategy, value object