Menerapkan Inheritance (Bagi Pemula)


Salah satu sifat OOP yang kerap mudah dicerna tetapi sulit diterapkan oleh pemula adalah inheritance.   Pada ruangan kelas, biasanya saya akan memakai Greenfoot yang dilengkapi animasi menarik untuk menunjukkan konsep dasar inheritance.   Selain itu, di buku-buku banyak dijumpai contoh klasik seperti Mobil, Motor dan Truk yang biasanya mudah dipahami.   Bila tidak cukup, ada contoh Binatang, Kucing, Burung, dan Ikan.   Tujuan dari semua contoh tersebut adalah untuk menunjukkan bagaimana cara kerja inheritance.   Tapi beberapa pelajar yang kritis selalu bertanya: seperti apa penerapan inheritance di kasus yang lebih serius selain kendaraan dan binatang?

Berikut adalah contoh sebuah class diagram yang menunjukkan inheritance:

Contoh Inheritance

Contoh Inheritance

Pada hampir semua jenis faktur, biasanya kita akan menjumpai beberapa atribut yang sama.   Misalnya nomor, tanggal dan detail baris per baris.   Selain itu, hampir semua faktur juga memiliki operasi yang sama, seperti menghitung total, menambah item, hapus, dan sebagainya.   Oleh sebab itu, saya meletakkan seluruh atribut dan operasi yang sama ke sebuah class yang diberi nama Faktur.   Seluruh turunan dari class Faktur akan memiliki atribut dan operasi miliknya, termasuk juga ‘cucu’-nya: FakturJual dan FakturBeli. Dengan demikian, saya bisa membuat kode program seperti:

FakturMutasi m = new FakturMutasi()
m.nomor = ...
m.tanggal = ...
m.detail = ...
m.total()

FakturJual j = new FakturJual()
j.nomor = ...
j.tanggal = ...
j.detail = ...
j.total()

FakturBeli b = new FakturBeli()
b.nomor = ...
b.tanggal = ...
b.detail = ...
b.total()

Apa manfaatnya? Setiap kali menambah class yang mewakili faktur baru, saya hanya perlu menurunkan class tersebut dari class Faktur dan secara otomatis class baru tersebut telah memiliki seluruh atribut dan operasi dasar untuk sebuah faktur. Ini adalah apa yang disebut memakai yang sudah ada tanpa copy paste, atau bahasa kerennya: reusable.

Pertanyaannya sekarang, apakah kode berikut ini valid?

Faktur f = new Faktur()
f.nomor = ...
f.detail = ...
f.total()

Variabel f merujuk ke faktur apa? Pembelian? Penjualan? Mutasi? Tidak jelas! Oleh sebab itu, saya membuat class Faktur dan FakturPembayaran menjadi abstract class. Sebuah abstract class adalah class yang tidak boleh dipakai secara langsung, melainkan harus dipakai melalui turunannya. Sebagai contoh:

Faktur f = new Faktur()  // <-- ERRROR!

Faktur m = new FakturMutasi()
Faktur j = new FakturJual()
Faktur b = new FakturBeli()

FakturPembayaran p = new FakturPembayaran()  // <-- ERROR!

FakturPembayaran pj = new FakturJual()
FakturPembayaran pb = new FakturBeli()

Apa manfaat abstact class? Hmmh.. Berjaga-jaga agar saya atau rekan kerja saya yang sedang ngantuk tidak secara sengaja membuat instance baru sebuah class! Bila semua developer memiliki instinct Spiderman sehingga mereka tidak pernah ceroboh dalam membuat kode program, maka abstract class tidak perlu ada di dunia ini😉

Biasanya pelajar akan cepat sekali menangkap atribut yang sama di faktur, tapi ada satu calon operasi yang sering terlupakan. Apakah operasi yang selalu ada pada seluruh faktur tapi belum saya tuliskan di atas? Perubahan nilai stok (inventory)! Seluruh faktur akan mempengaruhi nilai inventory, hanya saja dengan nilai berbeda. Misalnya faktur jual akan mengurangi jumlah stok sementara faktur beli akan meningkatkan jumlah stok. Sepertinya ini adalah sebuah proses yang sama dengan nilai yang berbeda, bukan?

Oleh sebab itu saya menambahkan method konversiStok() di class Faktur. Method ini adalah abstract method. Dengan demikian, tidak ada implementasi kode programnya disini. Tapi, seluruh turunan class Faktur yang bukan abstract class wajib mendefinisikan method ini. Misalnya:

abstract class Faktur {

  String nomor

  ...

  // Mengembalikan perubahan stok (bisa plus atau minus) berdasarkan
  // total kuantitas faktur
  abstract Integer konversiStok()

}

class FakturMutasi extends Faktur {

  ...

  Integer konversiStok() {
     return -totalKuantitas()
  }

}

class FakturJual extends FakturPembayaran {

  ...

  Integer konversiStok() {
     return -totalKuantitas()
  }

}

class FakturBeli extends FakturPembayaran {

  ...

  Integer konversiStok() {
     return totalKuantitas()
  }
}

Perhatikan bahwa class FakturPembayaran tidak perlu membuat implementasi konversiStok() karena class tersebut juga merupakan class abstract. Tapi kewajiban untuk menentukan pengaruh stok tetap berlaku bagi seluruh turunan class FakturPembayaran yang tidak abstract. Jadi, apa yang didefinisikan di class Faktur berlaku turunan temurun tanpa pandang bulu termasuk sampai ke seluruh cucu dan cicitnya nanti!

Apa manfaatnya? Setiap class konkrit dari Faktur memberi tahu apa pengaruh masing-masing dari mereka terhadap kuantitas di stok. Dengan demikian, pada saat perhitungan kuantitas stok, saya cukup memakai kode program seperti:

void perubahanStok(Faktur faktur) {
  if (!faktur.hapus) {
      jumlahStok = jumlahStok + faktur.konversiStok()
  } else {
      jumlahStok = jumlahStok - faktur.konversiStok()
  }
}

Kode program di atas jauh lebih mudah dipahami, lebih simple (ingat prinsip KIS!), dan lebih aman terhadap kesalahan, bila dibandingkan dengan versi prosedural berikut ini:

//
// Ini contoh yang tidak disarankan!
//
if (tambahFaktur) {
  if (pembelian) {
    jumlahStok = jumlahStok + totalKuantitasFaktur
  } else if (penjualan) {
    jumlahStok = jumlahStok - totalKuantitasFaktur
  } else if (mutasi) {
    jumlahStok = jumlahStok - totalKuantitasFaktur
  }
} else if (hapusFaktur) {
  if (pembelian) {
    jumlahStok = jumlahStok - totalKuantitasFaktur
  } else if (penjualan) {
    jumlahStok = jumlahStok + totalKuantitasFaktur
  } else if (mutasi) {
    jumlahStok = jumlahStok + totalKuantitasFaktur
  }
}

P.S: Bicara soal prinsip KIS (Keep It Simple), sebenarnya istilah ini relatif terhadap pengetahuan developer terkait. Seorang developer yang terbiasa dengan konsep prosedural dan modular akan berteriak: “KIS!” saat melihat rancangan OOP yang mengklasifikasikan (arti kata ‘class’) masalah dalam banyak class-class terpisah.

Beberapa buku OOP yang saya baca menyarankan bahwa bila dijumpai banyak if seperti di atas, biasanya adalah calon yang dimana inheritance dapat diterapakan.

Sekarang mari mengasah kemampuan memikirkan kode program secara vertikal sekali lagi! Seandainya, saya memiliki class sederhana dengan rancangan seperti berikut ini:

Contoh rancangan dengan total()

Contoh rancangan dengan total()

Anggap saja saya sudah membuat implementasi kode program yang memakai class tersebut dan sudah berjalan selama berbulan-bulan. Lalu tiba-tiba saya menyadari bahwa ada sesuatu yang kurang: seluruh faktur beli maupun faktur jual dapat memiliki potongan retur. Nilai potongan retur ini akan mempengaruhi total! Sementara itu, method Faktur.total() sudah dipakai dimana-mana termasuk di laporan. Apa yang harus saya lakukan? Berikut adalah permasalahannya:

  1. Atribut potongan retur hanya berlaku untuk faktur beli dan faktur jual, dengan demikian ia adalah kandidat yang tepat untuk diletakkan di class FakturPembayaran. Tapi method total() ada di Faktur. Padahal potongan retur mempengaruhi perhitungan total.
  2. Bila saya meletakkan potongan retur di class Faktur, maka seluruh turunan class Faktur akan memiliki potongan retur. Bukankah ini terlalu berlebihan?
  3. Jadi?

Solusinya adalah dengan men-override method total() milik Faktur di FakturPembayaran. Bagi yang bingung, ingat contoh klasik: Binatang bisa bersuara, kucing akan mengeong dan anjing akan mengonggong. Bila diterapkan ke kasus ini: Faktur bisa menghitung total, dan FakturPembayaran memiliki perhitungan totalnya sendiri yang melibatkan potongan retur.

Override total() milik Faktur di FakturPembayaran

Override total() milik Faktur di FakturPembayaran

Berikut adalah contoh implementasinya:

abstract class Faktur {

  ...

  BigDecimal total() {
     ... // hitung berdasarkan seluruh item dan diskon
  }

}

class FakturPembayaran extends Faktur {

  ...

  BigDecimal potonganRetur

  BigDecimal total() {
     super.total() - (potonganRetur?: 0)
  }

}

Apa manfaatnya? Dari sisi penggunaan class, dampak perubahan hanya berpengaruh terhadap FakturBeli dan FakturJual saja. Dengan meng-enkapsulasi permasalahan sehingga membatasi ruang lingkup dampaknya, maka tingkat kesalahan akibat hal tak terduga dari perubahan bisa dikurangi. Misalnya:

Faktur m = new FakturMutasi()
m.total()  // <-- memanggil Faktur.total()

Faktur fj = new FakturJual()
fj.total()  // <-- memanggil FakturPembayaran.total()

Faktur fb = new FakturBeli()
fb.total()  // <-- memanggil FakturPembelian.total()

Bagi pemula, pemanggilan kode program secara vertikal memang sesuatu yang aneh bila dibandingkan dengan memanggil method per modul atau per class. Tapi bila diterapkan dengan benar, bisa jadi inheritance akan menjauhkan yang bersangkutan dari banyak masalah di kemudian hari😉

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: