Design patterns implemented in Scala
Patterns
Creational patterns
Singleton
Explanation
It is useful when only one instance should be created in the application, for example utility classes, data sources. The object's creation is lazy, i.e. it is not created when not accessed.
Implementation
In Scala singleton can be created using the object
keyword:
object StringUtils {
println("Creating StringUtils")
def isPalindrome(s: String) = s.zip(s.reverse)
.forall { case (c1, c2) => c1 == c2 }
}
Usage
object Client {
def main(args: Array[String]): Unit = {
println(StringUtils.isPalindrome("akka"))
// Creating StringUtils
// true
println(StringUtils.isPalindrome("scala"))
// false
}
}
The object is initialized only once — during the first call (Creating StringUtils
is printed only once, after calling StringUtils.isPalindrome
).
If the object was not used, the initialization would not occur.
Reference
- Scala tour - singleton objects
- Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Monostate
Explanation
According to R. Martin monostate has the same behavior as singleton, but does not require creating only one instance of the class. This means, that multiple different instances of a single class should share state.
Implementation
In languages like Java this is easy to implement using static variables (although this is considered a code smell).
Since scala does not have the concept of static variables, companion objects can be used.
(There is a proposal of @static
annotation for Scala.)
class Counter {
def increment = Counter.x += 1
def get = Counter.x
}
object Counter {
private var x: Int = 0
}
Usage
test("Instances should share state") {
val (c1, c2) = (new Counter(), new Counter())
assert(!c1.eq(c2), "Two different instances are created")
c1.increment
assert(c1.get == c2.get, "They share the same state")
assert(c2.get == 1, "The counter of c2 was incremented on c1.increment call")
}
Although different instances were created, they share the same state.
The behavior is the same as of singleton.
The difference is in how the client code uses it — in monostate the fact, that the state is shared is hidden from the client (constructor is used instead of object
or getInstance
method).
Singleton fulfills the definition with the property of multiple instances relaxed.
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Factory method
Explanation
Factory method implements logic associated with creating subclasses of a certain class.
Implementation
Consider the following type hierarchy:
sealed abstract class Shape
case class Circle(radius: Double) extends Shape
case class Square(side: Double) extends Shape
The creation is declared in ShapeProvider
interface and implemented in CircleProvider
and SquareProvider
:
trait ShapeProvider {
def getShape(diameter: Double): Shape
}
class CircleProvider extends ShapeProvider {
override def getShape(diameter: Double): Shape = Circle(diameter / 2)
}
class SquareProvider extends ShapeProvider {
override def getShape(diameter: Double): Shape = Square(diameter / math.sqrt(2))
}
Usage
test("should decide using inheritance") {
val shape = new SquareProvider().getShape(.5)
assert(shape.isInstanceOf[Square])
}
Reference
Gamma, Erich, et al. Design Patterns: Elements of Reusable Object-Oriented Software. 1994.
Structural patterns
Facade
Explanation
Facade eases the usage of some complex API.
Implementation
Suppose we have a service for the deposit and withdrawal of money and lookup of balance:
object BankingService {
val accountsBalance: mutable.Map[Int, Double] = mutable.Map().withDefaultValue(0)
def deposit(accountId: Int, amount: Double) = accountsBalance.update(accountId, accountsBalance.getOrElse(accountId, 0d) + amount)
def withdraw(accountId: Int, amount: Double) = accountsBalance.update(accountId, accountsBalance.getOrElse(accountId, 0d) - amount)
def getBalance(accountId: Int) = accountsBalance(accountId)
}
Transferring money between accounts would require the client to subsequently call BankingService.withdraw
and BankingService.deposit
and make sure both run within a single transaction, so that no money is lost.
We can provide a facade, that eases takes the responsibility of transferring and easing API usage:
object BankingFacade {
def transfer(from: Int, to: Int, amount: Double) = {
withinTransaction {
BankingService.withdraw(from, amount)
BankingService.deposit(to, amount)
}
}
// dummy method that should wrap a transaction context
private def withinTransaction(r: => Unit) = r
}
Now all there operations are as easy as invoking BankingFacade.transfer
.
Usage
before {
BankingService.deposit(1, 100)
BankingService.deposit(2, 100)
}
test("should transfer money between accounts") {
BankingFacade.transfer(1, 2, 25)
assert(BankingService.getBalance(2) == 125d)
}
Client only needs to invoke BankingFacade.transfer
function, that takes the responsibility of transactional withdrawal and deposit.
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Adapter
Explanation
Adapter is used to facilitate the communication between two systems by providing by providing a common interface. An example is connecting to a legacy system, whose API does not conform to ours.
Implementation
Suppose our system uses the following trait for services making web requests:
trait WebRequester {
def request(obj: Any): String
}
And we need to integrate an external / legacy service with different API:
class LegacyWebRequester {
def sendRequest(json: String) = "200 OK"
}
With adapter we can wrap the service (adaptee) and expose the target interface to the client:
class WebRequesterAdapter(legacyWebRequester: LegacyWebRequester) extends WebRequester {
override def request(obj: Any): String = {
val request = toJson(obj)
legacyWebRequester.sendRequest(request)
}
def toJson(obj: Any) = obj.toString
}
In this case the service is stateless, so we can leverage Scala's singleton object
:
object StatelessWebRequesterAdapter extends WebRequester {
val legacyWebRequester = new LegacyWebRequester
override def request(obj: Any): String = {
val request = toJson(obj)
legacyWebRequester.sendRequest(request)
}
def toJson(obj: Any) = obj.toString
}
This will provide a single entry for the legacy module.
Usage
The legacy service needs to be wrapped into the adapter, so that the trait's method can be invoked:
object Client {
def main(args: Array[String]): Unit = {
val webRequester: WebRequester = new WebRequesterAdapter(new LegacyWebRequester)
println(webRequester.request(42))
println(StatelessWebRequesterAdapter.request(42))
}
}
In this case the adaptar can instantiate the adaptee, so that it's invisible to the client.
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Composite
Explanation
Composite pattern allows nesting structures and dealing with them uniformly.
Implementation
A great example of composite pattern is a tree. A tree can be a leaf or a branch, which itself is also a tree (a subtree of it root). The following snippet is an example of binary tree used to sort an array stored in leaves.
sealed abstract class BinaryTree {
def sorted: List[Double]
}
case class Branch(left: BinaryTree, right: BinaryTree) extends BinaryTree {
override def sorted: List[Double] = sortRecursively(left.sorted, right.sorted)
private def sortRecursively(left: List[Double], right: List[Double]): List[Double] = {
if (left.isEmpty && right.isEmpty) List()
else if (left.isEmpty) right.sorted
else if (right.isEmpty) left.sorted
else if (left.head <= right.head) left.head :: sortRecursively(left.tail, right)
else right.head :: sortRecursively(left, right.tail)
}
}
case class Leaf(ar: List[Double]) extends BinaryTree {
override def sorted: List[Double] = ar.sorted
}
Both Branch
and Leaf
can be sorted.
This is a common behavior defined in the abstract superclass BinaryTree
.
Array sorting is done recursively and propagates from root to leaves.
Usage
Any array can be represented as a tree. Having a tree we can sort it in the following way:
test("Should return sorted array") {
val tree = Branch(
Branch(
Leaf(List(1, 4, 8)),
Branch(
Leaf(List(2, 6)),
Leaf(List(7))
)
),
Branch(
Leaf(List(3, 9)),
Leaf(List(5))
)
)
val sorted = tree.sorted
assert(sorted == List(1, 2, 3, 4, 5, 6, 7, 8, 9))
}
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Proxy
Explanation
Proxy wraps an object and delegates to it while adding additional logic. Most common use cases are restricting access (proxy checks permissions to the underlying object), dirty checking (many ORMs are keeping track of changes in the entities during transaction, so that no unnecessary updates are triggered) or, as in the following example, gathering statistics (proxy counts the number of database accesses).
Implementation
Suppose we have a DataSource
trait, which defines database access.
trait DataSource {
def executeQuery(query: String)
}
SimpleDataSource
is an object used in production code.
object SimpleDataSource extends DataSource {
override def executeQuery(query: String): Unit = println(query)
}
CountingDataSource
proxy can be used when testing to keep track of the database load.
object CountingDataSource extends DataSource {
private var counter = 0
override def executeQuery(query: String): Unit = {
counter += 1
SimpleDataSource.executeQuery(query)
}
def getNumberOfDbQueries = counter
}
Usage
Every query to the database is recorded.
This can help avoiding excessive database load problems, for example N+1
select problem.
test("Should count number of db shots") {
CountingDataSource.executeQuery("select * from people;")
CountingDataSource.executeQuery("insert into people(id, name) values (1, 'John Doe')")
assert(CountingDataSource.getNumberOfDbQueries == 2)
}
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Decorator
Explanation
Decorator allows stacking implementations to add additional features at runtime.
Implementation
Suppose we have a WebPage
interface:
trait WebPage {
def render();
}
And its implementation:
class SimpleWebPage extends WebPage {
override def render(): Unit = println("Rendered page")
}
We can define an interface for the decorators. The decorator delegates the basic logic to the object it decorates.
abstract class WebPageDecorator(webPage: WebPage) extends WebPage {
override def render(): Unit = webPage.render()
}
Now decorators can be implemented to decorate SimpleWebPage
with additional logic:
class AuthorizedWebPage(webPage: WebPage) extends WebPageDecorator(webPage) {
override def render(): Unit = {
super.render()
println("Authorizing...")
}
}
class AuthenticatedWebPage(webPage: WebPage) extends WebPageDecorator(webPage) {
override def render(): Unit = {
super.render()
println("Authenticating...")
}
}
Usage
When decorating SimpleWebPage
and running render
, the implementations from subsequent decorators is invoked:
object Client {
def main(args: Array[String]): Unit = {
val webPage = new AuthenticatedWebPage(new AuthorizedWebPage(new SimpleWebPage))
webPage.render()
// Rendered page
// Authorizing...
// Authenticating...
}
}
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.
Behavioral patterns
Template method
Explanation
Template method defines a general algorithm, a scaffold, that is further implemented the subclasses. It abstracts a pattern that is common among the subclasses
Implementation
Suppose we would like to have different services for user notification. There are multiple channels: emails, SMS, etc. Each service performs the same general algorithm: checking whether the user is subscribed to this channel and sending the notification. The implementation can differ.
The scaffold is defined in a trait.
trait NotificationService {
protected def isSubscribed(userId: Long): Boolean
protected def sendNotification(userId: Long): Unit
def notifyUser(userId: Long): Unit = {
if (isSubscribed(userId)) sendNotification(userId)
else println("User not subscribed")
}
}
The notification logic is already defined, since it is the same for every channel.
The implementations only differ in isSubscribed
and sendNotification
methods.
object EmailNotifier extends NotificationService {
override protected def isSubscribed(userId: Long): Boolean = isSubscribedToEmail(userId)
override protected def sendNotification(userId: Long): Unit = sendEmail(userId)
private def sendEmail(userId: Long): Unit = println(s"Email to user ${userId}")
private def isSubscribedToEmail(userId: Long): Boolean = userId == 0
}
object WebpageNotifier extends NotificationService {
override protected def isSubscribed(userId: Long): Boolean = isSubscribedToSms(userId)
override protected def sendNotification(userId: Long): Unit = sendSms(userId)
private def sendSms(userId: Long): Unit = println(s"SMS to user ${userId}")
private def isSubscribedToSms(userId: Long): Boolean = userId == 1
}
Usage
The client only needs to invoke the template method defined in the trait.
object Client {
def main(args: Array[String]): Unit = {
EmailNotifier.notifyUser(1)
WebpageNotifier.notifyUser(1)
}
}
Reference
Martin, Robert Cecil. Agile Software Development, Principles, Patterns, and Practices. 2002.