Scala OOP: 9 things you forgot
Hello, dear Reader! I apologize for a such loud title of the article, but you should know how it’s important for me to engage the readers to discussion. Not so long time ago, I decided to systemize my knowledge of object oriented programming in Scala. That was awesome idea! And now I want to share with you all mini-topics which I underlined as important, but many developers forget about them.
Of course you will find that something is very trivial, but I bet you definitely will be surprised at least with one out of nine points. At least I hope so 🙂 Feel free to add your favorite Scala OOP tricks in the comments, if I didn’t mention them.
#1 Class constructors
Do you know how to create more than one constructor in a class or in a case class? Scala allows to do this. Buy the way you need to use this
keyword.
case class Account(id: String, balance: Double, status: Boolean) { def this(id: String) = this(id, 0, false) def this(id: String, balance: Double) = this(id, balance, false) def this(id: String, status: Boolean) = this(id, 0, status) } val a1 = new Account("a") // Account(a,0.0,false) val a2 = new Account("b", 100) // Account(b,100.0,false) val a3 = new Account("b", true) // Account(b,0.0,true) val a4 = new Account("b", 450, false) // Account(b,450.0,false)
Such constructor declaration can be applied either to common classes or to case classes. All constructors created with help of this
keyword are called secondary constructors.
Keep in mind that the same result can be achieved with help of default values declaration.
#2 Traits mixing during object initialization
Mixins in Scala are a common practice. You simply create multiple traits and then add them to a class declaration using with
keyword. But what if you want to apply traits dynamically depends on situation? It’s also possible!
class Worker(health: Int) { def mineMinerals() = s"Crafting minerals" } trait GasCrafter { def mineGas() = "Crafting gas..." } val universalWorker = new Worker(45) with GasCrafter universalWorker.mineMinerals() universalWorker.mineGas()
You can mix as many traits as you want. But as you may guess it’s very easy to write a code with long mixins and in a result lost its readability.
#3 Java developers & Scala OOP
Don’t start a holy war with addicted Java developers about advantages of Scala OOP. A concise Scala code doesn’t disturb them at all. For each your argument they have a separate library: auto-generation of getters and setters, implementation of immutability etc.
I hope this advice will save a lot of your time 😀
#4 Mutable case class arguments
Scala developers know that data implementation starts from case classes. Since Scala propagates immutability it’s ok that by default all arguments of a case class can not be overwritten. So when you want to change case class arguments after object creation, use var
keyword:
case class Person(name: String) val person = Person("Alex") person.name = "Bob" //Reassignment to val case class Friend(var name: String) val friend = Friend("Sam") friend.name= "Bob" //Ok friend.name // Bob
So here is everything absolutely up to you. If you want to close the eyes on Scala recommendations and best practices regarding the variables immutability it’s ok, simply use var
keyword in a case class declaration. But if you want to code in a functional style, the paragraph #6 is for you!
#5 Access modifiers
Scala has very agile system of access modifiers. Despite that their number less than in Java (public
is absent explicitly), they are more powerful and give more grained control over class and object fields. In a context of nested classes, inheritance and packages you can declare fields with such access modifiers:
package myPac class Foo { private[this] val a = 10 protected val b = 50 private[free] val c = 15 * commonConstant class FooInner { private[Foo] val d = 30 private[myPac] def doJob() = {} } }
In the square brackets we set a precise access level. So instead of default behavior of private
and protected
keywords we can increase or decrease their effect.
And here is a code for myPac
package:
package object myPac { val commonConstant = 100 }
#6 “Change” state of immutable objects
When you are trying to adhere functional programming principles you have to work with immutable values. But this does not mean that you don’t need to change these values. For example case classes have an auto-generated method copy
. It serves for creation of a new object based on old one. Here is a code sample:
case class Account(id: String, balance: Double, status: Boolean) def withdraw(amount: Double, acc: Account): Account = { acc copy(balance = acc.balance - amount) } def ban(acc: Account): Account = acc copy(status = false) def toDefault(acc: Account): Account = { acc copy(balance = 100, status = true) } val acc1 = Account("v0A0q1", 100, true) val accAfterWithdraw = withdraw(25, acc1) //Account(v0A0q1,75.0,true) val bannedAcc = ban(accAfterWithdraw) //Account(v0A0q1,75.0,false) val restoredToDefaultAcc = toDefault(bannedAcc) //Account(v0A0q1,100.0,true)
With help of copy method you can create new object by changing from 0 to N arguments.
#7 Methods apply & unapply
Frequently Scala developers call Scala a “data centric” programming language. In many senses this is due to case classes and operations which you can do with them. For each case class Scala generates two methods apply
& unapply
.
Using these methods you can extract arguments from a case class in a tuple form and vice versa.
case class Account(id: String, balance: Double, status: Boolean) val acc = Account("a01", 10, true) Account.unapply(acc) // Some((a01,10.0,true)) val dataSet = ("a01",10.0,true) (Account.apply _).tupled(dataSet) // Account(a01,10.0,true)
You must remember that unapply
is an extractor method. Thanks to it a pattern matching can be applied to the object with appropriate unapply
method.
#8 Keyword sealed
During a class (trait) creation you can decorate it with sealed
keyword. This circumstance applies one very important restriction to the class (trait) – it can not be extended outside of the source file where it is created.
sealed abstract class LegacyDriver { def connect(): Unit } class ReactiveDriver extends LegacyDriver { override def connect(): Unit = { println("Reactive connection...") } } class SQLDriver extends LegacyDriver { override def connect(): Unit = { println("SQL connection...") } }
In the code snippet above LegacyDriver
is marked with sealed
keyword. It’s ok to extend it directly inside of its source, but its extension doesn’t allowed outside of it. When sealed
keyword is useful? Well, when you create some particular data hierarchy, you may want to keep some implementations in one place. This is a good use case for sealed
.
#9 Object & companion
Scala has its own builtin mechanism for a singleton – object
keyword. You can declare an object and then use its members: values and methods. But what is the difference between an object and a companion object in Scala? A companion object is the object with the same name as a class which is created in the same source file.
case class Book(pages: Int, ratio: Int) object Book { def isGoodForJourney(book: Book) = { if (book.pages < 200 & book.ratio > 3) true else false } } val b1 = Book(190, 4) Book.isGoodForJourney(b1)
Summary
That’s it! I believe you have found something new for yourself or at least refreshed in memory a Scala OOP theory. If you liked the post, please share it on Twitter or Facebook. Next week I’m going to publish new article with analysis of Scala & Java development. So let’s stay in touch – subscribe for a newsletters!