OOP Concepts in Scala

As mentioned in the introduction, Scala combines object-oriented and functional programming paradigms.

Like many other object-oriented languages, Scala provides classes. In addition, Scala also provides traits.

Traits

Traits are like interfaces, but can also define concrete methods and properties.

trait Baddie;

Abstract Methods and Properties

Traits can define abstract members that are required to be implemented by any subclass.

The following is a simple example of a purely interface-like trait, providing only abstract methods:

 trait Pillager {   val target: String   def pillage: String } class Pirate extends Pillager {   def target = "other ships"   def pillage = "The pirate pillages " + target }
 trait Pillager:   val target: String   def pillage: String class Pirate extends Pillager:   def target = "other ships"   def pillage = "The pirate pillages " + target

Concrete Methods and Properties

Traits can also provide concrete methods and properties, basically members provided to the subclass by the trait.

 trait Pillager {   val name: String;   val target = "innocent people"   def pillage = s"The $name pillages $target" } class Bandit extends Pillager {   val name = "bandit" }
 trait Pillager:   val name: String;   val target = "innocent people"   def pillage = s"The $name pillages $target" class Bandit extends Pillager:   val name = "bandit"

Mixin Composition

A class (or trait) can also extend multiple traits to combine multiple behaviors.

 trait Pillager {   val target: String   def pillage: String } trait Sailor {   def sail = "They sail" } class Pirate extends Pillager with Sailor {   def target = "other ships"   def pillage = "The pirate pillages " + target }
 trait Pillager:   val target: String   def pillage: String trait Sailor:   def sail = "They sail" class Pirate extends Pillager, Sailor:   def target = "other ships"   def pillage = "The pirate pillages " + target

Classes

Classes are blueprints for objects. They can also extend traits or other classes (or both).

While a class can extend multiple traits, it can only extend one class. It is advisable to use traits over classes for decomposition.

Let's rewrite our previous example to use only classes.

 // superclass class Pillager(val name: String) {   val target = "innocent people"   def pillage = s"The $name pillages $target" } // derived subclass class Bandit extends Pillager("bandit");
 // superclass class Pillager(val name: String):   val target = "innocent people"   def pillage = s"The $name pillages $target" // derived subclass class Bandit extends Pillager("bandit");

In this example, Pillager is the superclass from which the Bandit subclass is derived.

Classes can have a constructor which helps initialize values.

// Pirate is a class with one parameter, name: String// The `val` implies that the parameter is stored// as a property of the Pirate class.class Pirate(val name: String)// teach is an instance of Pirate with the name Blackbeardval teach = new Pirate("Blackbeard")

Abstract Classes

Abstract classes are similar to traits but with one big difference:

  • Code with traits cannot be called from Java code.

Abstract classes are capable of operating with Java.

 abstract class Pillager(val name: String) {   def target: String   def pillage = s"The $name pillages $target" } // derived subclass class Bandit extends Pillager("bandit") {   val target = "innocent people" }
 abstract class Pillager(val name: String):   def target: String   def pillage = s"The $name pillages $target" // derived subclass class Bandit extends Pillager("bandit") {   val target = "innocent people" }

Implicit Classes and Extension Methods

Imagine you didn't have to explicitly construct a class to use it.

Seems convenient, yet scary, right?

Implicit classes were used in Scala 2 to add more methods to existing data types.

object stuff {    def main(args: Array[String]): Unit = {        "steve".mine        // steve is automatically converted into a player    }    implicit class Player(name: String) {        def mine = println(s"${name} mines!")    }}

Scala 3 provides extension methods that can work on existing data types.

extension (name: String)    def mine = println(s"${name} mines!")@main def stuff(args: Array[String]): Unit = "steve".mine

Case Classes

Case classes are similar to regular classes, with more features:

  • All case classes have an apply and unapply method.

This means, they can be constructed without a new keyword and can be destroyed using a match expression.

val origin = Coordinates(83, 10, 23)origin match {    case Coordinates(x, y, z) => println(s"The altitude is ${y}!")}// CASE CLASSES HAVE// BUILT-IN EXTRACTORcase class Coordinates(x: Int, y: Int, z: Int)
val origin = Coordinates(83, 10, 23)origin match    case Coordinates(x, y, z) => println(s"The altitude is ${y}!")// CASE CLASSES HAVE// BUILT-IN EXTRACTORcase class Coordinates(x: Int, y: Int, z: Int)

Case Objects

Case objects are like case classes, but cannot be constructed.

trait Flavor;case object ButterScotch extends Flavorcase object Chocolate extends Flavorcase object Vanilla extends Flavorobject stuff {    def main(args: Array[String]): Unit = {        val flavor: Flavor = Chocolate        flavor match {            case ButterScotch => println("Tis yellow")            case Chocolate => println("Tis bitter")            case Vanilla => println("Tis heavenly")        }    }}
trait Flavor;case object ButterScotch extends Flavorcase object Chocolate extends Flavorcase object Vanilla extends Flavor@main def stuff(): Unit =    val flavor: Flavor = Chocolate    flavor match         case ButterScotch => println("Tis yellow")        case Chocolate => println("Tis bitter")        case Vanilla => println("Tis heavenly")