Scala Abstract Classes

An abstract class defines a blueprint that cannot be instantiated directly. It declares some members without implementing them — these are abstract members. Subclasses must provide implementations for all abstract members before they can be instantiated. Abstract classes combine shared behavior with enforced contracts.

Defining an Abstract Class

abstract class Shape(val color: String):
  def area(): Double       // abstract — no body
  def perimeter(): Double  // abstract — no body
  def describe(): String = // concrete — has a body
    f"$color ${getClass.getSimpleName}: area=${area()}%.2f, perimeter=${perimeter()}%.2f"

// val s = Shape("red")  // Error: class Shape is abstract; cannot be instantiated

Implementing an Abstract Class

class Circle(color: String, val radius: Double) extends Shape(color):
  override def area(): Double = Math.PI * radius * radius
  override def perimeter(): Double = 2 * Math.PI * radius

class Rectangle(color: String, val w: Double, val h: Double) extends Shape(color):
  override def area(): Double = w * h
  override def perimeter(): Double = 2 * (w + h)

class Triangle(color: String, val a: Double, val b: Double, val c: Double)
    extends Shape(color):
  override def area(): Double =
    val s = (a + b + c) / 2
    Math.sqrt(s * (s-a) * (s-b) * (s-c))  // Heron's formula
  override def perimeter(): Double = a + b + c

val shapes: List[Shape] = List(
  Circle("red", 5),
  Rectangle("blue", 4, 6),
  Triangle("green", 3, 4, 5)
)

shapes.foreach(s => println(s.describe()))
// red Circle: area=78.54, perimeter=31.42
// blue Rectangle: area=24.00, perimeter=20.00
// green Triangle: area=6.00, perimeter=12.00

     abstract Shape
    /       |       \
Circle  Rectangle  Triangle
area()  area()      area()       ← each provides its own
peri()  peri()      peri()       ← implementation
        describe()               ← shared (from Shape)

Abstract vals and vars

abstract class Animal:
  val name: String       // abstract val — no initializer
  val sound: String      // abstract val

  def speak(): Unit = println(s"$name says $sound")

class Dog(val name: String) extends Animal:
  val sound: String = "Woof"

class Cat(val name: String) extends Animal:
  val sound: String = "Meow"

Dog("Rex").speak()    // Rex says Woof
Cat("Luna").speak()   // Luna says Meow

Template Method Pattern

Abstract classes excel at defining an algorithm skeleton where subclasses fill in specific steps. This is called the Template Method Pattern:

abstract class DataProcessor:
  // Template method — defines the overall flow
  def process(data: List[Int]): List[Int] =
    val filtered  = filter(data)
    val sorted    = sort(filtered)
    val formatted = transform(sorted)
    formatted

  // Steps — subclasses decide the details
  def filter(data: List[Int]): List[Int]
  def sort(data: List[Int]): List[Int]
  def transform(data: List[Int]): List[Int]

class PositiveEvensProcessor extends DataProcessor:
  def filter(data: List[Int]): List[Int] = data.filter(n => n > 0 && n % 2 == 0)
  def sort(data: List[Int]): List[Int]   = data.sorted
  def transform(data: List[Int]): List[Int] = data.map(_ * 10)

val proc = PositiveEvensProcessor()
val input = List(-3, 8, 2, -1, 6, 4, -7, 10)
println(proc.process(input))
// List(20, 40, 60, 80, 100)

Abstract Class vs Trait


Feature                  Abstract Class           Trait
──────────────────────   ──────────────────────   ─────────────────────────
Constructor params?      Yes                      Yes (Scala 3), No (Scala 2)
Multiple inheritance?    No (one class only)      Yes (many traits)
Abstract members?        Yes                      Yes
Concrete members?        Yes                      Yes
When to use?             Base with constructor     Mixin behavior
                         Single parent hierarchy   Multiple parent types

Abstract Class with Constructor Logic

abstract class Investment(val principal: Double, val ratePercent: Double):
  require(principal > 0, "Principal must be positive")
  require(ratePercent > 0, "Rate must be positive")

  val rate: Double = ratePercent / 100

  def valueAfter(years: Int): Double   // abstract

class SimpleInterest(principal: Double, ratePercent: Double)
    extends Investment(principal, ratePercent):
  def valueAfter(years: Int): Double =
    principal + (principal * rate * years)

class CompoundInterest(principal: Double, ratePercent: Double)
    extends Investment(principal, ratePercent):
  def valueAfter(years: Int): Double =
    principal * Math.pow(1 + rate, years)

val simple   = SimpleInterest(10000, 8.0)
val compound = CompoundInterest(10000, 8.0)

for years <- List(1, 5, 10) do
  println(f"Year $years: Simple=₹${simple.valueAfter(years)}%,.0f  " +
          f"Compound=₹${compound.valueAfter(years)}%,.0f")
// Year 1:  Simple=₹10,800  Compound=₹10,800
// Year 5:  Simple=₹14,000  Compound=₹14,693
// Year 10: Simple=₹18,000  Compound=₹21,589

Partial Implementation

Abstract classes can implement some methods and leave others abstract. Subclasses only need to fill the remaining abstract members:

abstract class Logger:
  def format(message: String): String   // abstract

  def log(message: String): Unit =       // concrete — uses format
    println(format(message))

  def logAll(messages: List[String]): Unit =
    messages.foreach(log)               // reuses log, which uses format

class TimestampLogger extends Logger:
  def format(message: String): String =
    s"[${System.currentTimeMillis()}] $message"

class PrefixLogger(prefix: String) extends Logger:
  def format(message: String): String = s"[$prefix] $message"

val tLog = TimestampLogger()
val pLog = PrefixLogger("INFO")

tLog.log("Server started")         // [1720000000123] Server started
pLog.logAll(List("Step 1", "Step 2"))
// [INFO] Step 1
// [INFO] Step 2

Leave a Comment

Your email address will not be published. Required fields are marked *