Belajar Scala - Object-Oriented: Class, Trait & Object
Episode 8 of 23

Belajar Scala - Object-Oriented: Class, Trait & Object

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.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

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.

Class dan Konstruktor

Constructor Parameters

Di Scala, parameter constructor langsung ditulis setelah nama class. Keyword val/var menentukan apakah parameter menjadi field:

Class dengan parameter konstruktor
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 class
  • val 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 dan Konstruktor Tambahan

Body class dijalankan saat instance dibuat; constructor tambahan dideklarasikan dengan def this(...):

Constructor tambahan
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 kedua

Inheritance

Scala mendukung inheritance tunggal antar class dengan keyword extends:

Inheritance sederhana
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: Komposisi Perilaku

Mixin (Multiple Behavior)

Trait adalah unit perilaku yang bisa di-mix in ke class. Tidak seperti Java, trait bisa punya implementasi dan state:

Dua trait sebagai mixin
trait Swimmer:
  def swim(): String = "berenang..."
 
trait Flyer:
  def fly(): String = "terbang..."
 
class Duck extends Swimmer with Flyer

Duck 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.

Abstract Member dan Implementasi

Trait bisa punya member abstrak (kontrak) dan member konkret (implementasi bawaan):

Trait dengan member abstrak dan konkret
trait Greeter:
  def name: String                    // abstrak
  def greet(): String = s"Hello, $name!"  // konkret
 
class Person(val name: String) extends Greeter

Person 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: Singleton dan Companion

Object Singleton

object membuat singleton — satu instance global yang dibuat otomatis saat pertama diakses:

Object singleton
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).

Companion Object

object dengan nama yang sama dengan class disebut companion — keduanya saling mengakses member privat:

Class + companion object
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.

apply: Factory Tanpa Keyword new

Companion object bisa mendefinisikan method apply, yang membuat pemanggilan tanpa new:

apply sebagai factory
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.

Merangkai: Class + Trait + Object

Dalam satu contoh realistis, ketiganya bekerja sama:

Merangkai ketiganya
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.

Common Pitfalls

  • new yang tidak perlu — untuk case class dan class dengan companion apply, jangan pakai new.
  • Inheritance berlebihan — jika perilaku bisa di-mix via trait, pilih trait; deep class hierarchy sulit dimodifikasi.
  • Lupa 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).

Penutup

Inti yang harus dibawa pulang:

  • Constructor parameters dengan val/var menentukan field publik; tanpa keduanya hanya tersedia di body.
  • Inheritance tunggal dengan extends; override wajib saat menimpa method.
  • Trait adalah mekanisme mixin untuk komposisi perilaku — multiple inheritance yang aman.
  • 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 toolingbuild.sbt, struktur project, sbt run/test/compile, plus Scala CLI dan Mill sebagai alternatif. Sampai jumpa di episode 9!