Menguasai sisi object-oriented Scala: class dengan constructor params, inheritance, trait sebagai mekanisme mixin untuk komposisi perilaku yang lebih fleksibel daripada multiple inheritance, serta object singleton dan companion yang menghubungkan statika dengan instance.

Setelah di episode 7 kalian memodelkan domain dengan ADT, sekarang kita melengkapi sisi lain dari janji "OOP + FP" di Scala: object-oriented programming. Scala bukan sekadar bahasa fungsional yang kebetulan ada di JVM — ia adalah bahasa OOP penuh, dan pemahaman tentang class, trait, dan object akan membuat kalian nyaman membaca codebase produksi mana pun.
Mengapa episode ini penting? Karena di dunia nyata, kalian akan bergulat dengan kode yang memakai ketiganya sekaligus: case class untuk data (FP), trait untuk perilaku (komposisi), dan object untuk singletons dan helper. Memahami kapan memakai mana adalah keterampilan yang membedakan developer senior.
Di Scala, parameter constructor langsung ditulis setelah nama class. Keyword val/var menentukan apakah parameter menjadi field:
class User(val name: String, var score: Int, private val id: String)
val u = User("Sari", 100, "usr-1")
u.name // akses field - ok (val)
u.score // akses field - ok (var)
u.id // error - private, tidak bisa diakses luar classval name → field publik immutable.var score → field publik mutable.private val id → field privat, hanya bisa diakses dari dalam class.Parameter tanpa val/var hanya tersedia di dalam body class, tidak menjadi field.
Body class dijalankan saat instance dibuat; constructor tambahan dideklarasikan dengan def this(...):
class Rect(val w: Double, val h: Double):
val area: Double = w * h
def this(side: Double) = this(side, side)
val sq = Rect(5.0) // memakai constructor keduaScala mendukung inheritance tunggal antar class dengan keyword extends:
class Animal(val name: String):
def speak(): String = "..."
class Dog(name: String) extends Animal(name):
override def speak(): String = "woof!"
val d = Dog("Bobi")
d.speak() // woof!Keyword override wajib saat menimpa method — ini mencegah typo yang diam-diam membuat method baru alih-alih menimpa yang lama. Perhatikan bahwa constructor class induk harus dipanggil: extends Animal(name).
Trait adalah unit perilaku yang bisa di-mix in ke class. Tidak seperti Java, trait bisa punya implementasi dan state:
trait Swimmer:
def swim(): String = "berenang..."
trait Flyer:
def fly(): String = "terbang..."
class Duck extends Swimmer with FlyerDuck mendapatkan perilaku swim dan fly sekaligus — multiple inheritance dengan cara yang aman dan tanpa konflik diamond yang memusingkan. Ini keunggulan trait dibanding inheritance class tunggal.
Trait bisa punya member abstrak (kontrak) dan member konkret (implementasi bawaan):
trait Greeter:
def name: String // abstrak
def greet(): String = s"Hello, $name!" // konkret
class Person(val name: String) extends GreeterPerson hanya perlu menyediakan name; greet datang gratis dari trait. Pola ini membuat trait menjadi alat komposisi yang sangat fleksibel.
Tip
Aturan praktis: gunakan trait untuk perilaku (apa yang bisa dilakukan), dan case class/class untuk data dan state. Kombinasi trait + case class (trait sebagai kontrak, case class sebagai implementasi data) adalah pola yang sangat umum di codebase Scala nyata.
object membuat singleton — satu instance global yang dibuat otomatis saat pertama diakses:
object Config:
val appName = "scala-app"
def version(): String = "1.0.0"
println(Config.appName)Tidak ada keyword static di Scala — object menggantikannya, dengan keunggulan tambahan: ia bisa memakai trait dan memiliki perilaku penuh (bukan sekadar method statis).
object dengan nama yang sama dengan class disebut companion — keduanya saling mengakses member privat:
class Account private (val id: String, val balance: Double)
object Account:
private val accounts = scala.collection.mutable.Map[String, Account]()
def open(id: String, initial: Double): Account =
val acc = Account(id, initial)
accounts(id) = acc
acc
def total(): Double = accounts.values.map(_.balance).sum
val a = Account.open("acc-1", 1000.0)Di sini: constructor Account dibuat private, sehingga satu-satunya cara membuat instance adalah lewat Account.open — companion punya hak akses ke konstruktor privat. Ini pola "factory" yang sangat bersih: factory, validasi, dan pembuatan instance terpusat di satu tempat.
Companion object bisa mendefinisikan method apply, yang membuat pemanggilan tanpa new:
object User:
def apply(id: Int, name: String): User = new User(id, name)
val u = User(1, "Sari") // memanggil User.apply(1, "Sari")Inilah yang membuat case class bisa dibuat tanpa new — compiler otomatis membuatkan apply di companion-nya.
Dalam satu contoh realistis, ketiganya bekerja sama:
trait Loggable:
def log(msg: String): Unit = println(s"[${getClass.getSimpleName}] $msg")
abstract class Job(val name: String) extends Loggable:
def run(): Unit
class ReportJob extends Job("report"):
override def run(): Unit =
log("menjalankan report...")
object JobRunner:
def execute(job: Job): Unit =
job.log(s"memulai ${job.name}")
job.run()
JobRunner.execute(ReportJob())trait Loggable memberi kemampuan log ke semua kelas.abstract class Job mendefinisikan kontrak pekerjaan.object JobRunner menyediakan entry point statis.new yang tidak perlu — untuk case class dan class dengan companion apply, jangan pakai new.override — Scala memaksa keyword ini; terbiasalah selalu menuliskannya saat menimpa method.object dengan state mutable yang besar — singleton global itu state global; waspadai saat berurusan dengan concurrency (episode 12).Inti yang harus dibawa pulang:
val/var menentukan field publik; tanpa keduanya hanya tersedia di body.extends; override wajib saat menimpa method.object = singleton; companion object = tempat factory dan member statis yang bisa mengakses member privat class-nya.apply memungkinkan konstruksi tanpa new — seperti yang dilakukan case class.Di episode 9 selanjutnya, kita beranjak dari file tunggal ke proyek sungguhan: sbt dan build tooling — build.sbt, struktur project, sbt run/test/compile, plus Scala CLI dan Mill sebagai alternatif. Sampai jumpa di episode 9!