Object-Oriented Programming (OOP) Core & Best Practices
Sebelum membangun sistem skala besar, seorang Backend Engineer wajib menguasai fondasinya terlebih dahulu. Ibarat membangun gedung pencakar langit, fondasi yang kuat akan membuat sistem tidak mudah "roboh" saat menghadapi perubahan kode yang besar.
Mari kita bahas Object-Oriented Programming (OOP) menggunakan analogi kehidupan nyata. OOP adalah paradigma pemrograman yang mengorganisir kode sebagai kumpulan objek yang saling berinteraksi — bukan sekadar urutan instruksi berjalan dari atas ke bawah.
Analogi: Seperti Membuat Cetakan Kue (Blueprint)
Bayangkan Anda ingin membuat kue pabrikan. Anda tidak langsung mencampur adonan sembarangan setiap kali membuat kue, melainkan membuat cetakan kuenya (Class) terlebih dahulu. Dari satu cetakan itu, Anda bisa mencetak ribuan kue dengan rasa dan bentuk yang sama persis, tetapi berdiri sebagai kue masing-masing (Object).
// Class = cetakan / blueprint
class Kue {
String rasa;
int ukuranCm;
void sajikan() {
System.out.println("Menghidangkan kue rasa " + rasa);
}
}
// Object = kue jadi hasil cetakan
Kue kue1 = new Kue();
kue1.rasa = "coklat";
kue1.sajikan();
Di bahasa pemrograman seperti Java atau PHP, OOP membantu kita merapikan kode agar tidak berantakan menggunakan 4 Pilar Utama.
1. Encapsulation (Bungkus & Amankan Data)
Ilustrasi: Seperti mesin ATM. Anda tidak bisa langsung mengambil uang dari brankas bank secara fisik; Anda harus melalui tombol mesin ATM (method) dengan memasukkan PIN.
Dalam Backend: Data sensitif (seperti password atau saldo user) disembunyikan menggunakan modifier private dan hanya bisa diakses lewat pintu resmi (getter/setter). Ini mencegah manipulasi data yang tidak sah dan menjaga integritas state.
public class Rekening {
private double saldo; // private: tidak bisa diakses dari luar
public Rekening(double saldoAwal) {
this.saldo = saldoAwal;
}
// Getter: pintu resmi untuk MEMBACA
public double getSaldo() {
return saldo;
}
// Method bisnis: pintu resmi untuk MENGUBAH (dengan validasi)
public void tarik(double jumlah) {
if (jumlah > saldo) {
throw new IllegalArgumentException("Saldo tidak cukup");
}
saldo -= jumlah;
}
}
Perhatikan: kita tidak menyediakan setSaldo() secara bebas. Semua perubahan saldo harus lewat method tarik() yang sudah memvalidasi. Inilah inti Encapsulation — state tidak bisa diubah sembarangan oleh kelas lain.
Hak Akses Modifier
Java menyediakan access modifier dengan level visibilitas berbeda:
| Modifier | Satu Class | Package | Subclass | Semua |
|---|---|---|---|---|
private | ✔ | ✘ | ✘ | ✘ |
| default (tanpa kata kunci) | ✔ | ✔ | ✘ | ✘ |
protected | ✔ | ✔ | ✔ | ✘ |
public | ✔ | ✔ | ✔ | ✔ |
Aturan praktis backend: mulai semua field dengan private, lalu buka akses secara sengaja lewat method. Field public adalah sumber bug halus — siapa pun bisa mengubah nilainya tanpa validasi.
2. Inheritance (Pewarisan Sifat)
Ilustrasi: Seorang anak mewarisi sifat dari orang tuanya.
Dalam Backend: Jika Anda memiliki Class utama bernama Karyawan, Anda bisa menurunkan sifatnya ke Class turunan seperti KaryawanTetap dan KaryawanKontrak tanpa harus menulis ulang fungsi menghitung gaji dari nol. Inilah penerapan Prinsip DRY - Don't Repeat Yourself.
// Parent / Super Class
public class Karyawan {
protected String nama;
protected double gajiPokok;
public double hitungGaji() {
return gajiPokok;
}
}
// Child / Sub Class — mewarisi field & method dari Karyawan
public class KaryawanTetap extends Karyawan {
private double tunjangan;
@Override
public double hitungGaji() {
return gajiPokok + tunjangan;
}
}
public class KaryawanKontrak extends Karyawan {
private int durasiBulan;
@Override
public double hitungGaji() {
return gajiPokok / durasiBulan;
}
}
Inheritance menghindari duplikasi kode: nama, gajiPokok, dan seluruh struktur dasar cukup ditulis sekali di parent. Perilaku yang berbeda diletakkan di masing-masing child.
3. Polymorphism (Banyak Bentuk)
Ilustrasi: Tombol "Enter" pada keyboard komputer. Tombolnya hanya satu, tetapi fungsinya bisa berubah-ubah tergantung aplikasi yang sedang Anda buka (di Microsoft Word untuk membuat baris baru, di formulir login untuk mengirim data).
Dalam Backend: Satu method yang sama dapat berperilaku berbeda tergantung objek apa yang memanggilnya. Polymorphism terwujud lewat dua cara:
Method Overloading (compile-time polymorphism)
Nama method sama, parameter berbeda — dalam satu kelas yang sama:
public class Notifikasi {
public void kirim(String pesan) { /* ke semua channel default */ }
public void kirim(String pesan, String email) { /* ke email */ }
public void kirim(String pesan, String email, String wa) { /* ke keduanya */ }
}
Method Overriding (runtime polymorphism)
Override = mengubah perilaku method milik parent di child class. Contoh paling powerful adalah polymorphism lewat tipe parent:
List<Karyawan> tim = List.of(new KaryawanTetap(...), new KaryawanKontrak(...));
for (Karyawan k : tim) {
// Method yang sama, perilaku berbeda tergantung objek aslinya
System.out.println(k.hitungGaji());
}
Di sini variabel bertipe Karyawan, tetapi method hitungGaji() yang jalan adalah versi milik subclass masing-masing. Inilah yang membuat kode backend menjadi extensible: untuk menambah jenis karyawan baru, Anda cukup membuat subclass baru tanpa menyentuh kode loop di atas.
4. Abstraction (Menyembunyikan Kerumitan)
Ilustrasi: Saat menyetir mobil, Anda cukup menginjak pedal gas tanpa harus pusing memikirkan bagaimana bensin diledakkan di dalam mesin.
Dalam Backend: Kita menyembunyikan logika rumit di balik sebuah Interface atau Abstract Class, sehingga programmer lain cukup tahu kontraknya tanpa perlu pusing melihat isi kodenya.
// Interface = kontrak "bisa melakukan apa"
public interface PaymentGateway {
PaymentResult charge(PaymentRequest request);
void refund(String transactionId);
}
// Implementasi 1: bayar lewat bank lokal
public class LocalBankGateway implements PaymentGateway { ... }
// Implementasi 2: bayar lewat e-wallet
public class EWalletGateway implements PaymentGateway { ... }
// Pemakai cukup tahu kontraknya, tidak peduli implementasinya
public class CheckoutService {
private final PaymentGateway gateway; // DI injection
public CheckoutService(PaymentGateway gateway) {
this.gateway = gateway;
}
public PaymentResult checkout(PaymentRequest req) {
return gateway.charge(req); // kita tak perlu tahu detailnya
}
}
Abstract Class vs Interface
| Aspek | Abstract Class | Interface |
|---|---|---|
| Mampu menyimpan state (field) | Ya | Hanya konstanta |
| Constructor | Bisa punya | Tidak |
| Multiple inheritance | Tidak (satu parent) | Bisa (implements banyak) |
| Kapan dipakai | Ada "hubungan IS-A" kuat & state dibagikan | Hanya mendefinisikan kontrak perilaku |
Aturan praktis: gunakan Interface untuk mendefinisikan kontrak perilaku (paling umum di backend), dan Abstract Class bila ada state/default behavior yang ingin dibagikan antar child.
Prinsip Clean Code Dasar
Dua prinsip minimal yang wajib dikuasai sejak dini:
1. Penamaan yang Deskriptif
Nama variabel, method, dan kelas harus menceritakan tujuannya tanpa perlu membaca isinya:
// Buruk
int d = 86400;
void p(int x) { ... }
// Baik
int maxRetrySeconds = 86400;
void retryPayment(int paymentId) { ... }
Nama method idealnya berbentuk kata kerja: getSaldo(), validateEmail(), cancelOrder(). Nama kelas berupa kata benda: OrderService, PaymentGateway, UserRepository.
2. Single Responsibility (tingkat fungsi)
Satu method sebaiknya melakukan satu hal saja, dan melakukannya dengan baik:
// Buruk: satu method mengerjakan banyak tugas
void saveUser(String email, String password) {
validateEmail(email);
hashPassword(password);
saveToDb(email, password);
sendWelcomeEmail(email); // eh, ini urusan yang lain
updateCache(email); // dan ini juga
}
// Baik: masing-masing punya satu tanggung jawab
void registerUser(String email, String password) {
validateEmail(email);
String hashed = hashPassword(password);
userRepository.save(email, hashed);
}
Ketika sebuah method berubah karena alasan yang berbeda-beda, itu tanda ia melanggar Single Responsibility. Pecah menjadi method-method kecil yang mudah diuji.
Kesimpulan
Empat pilar OOP bukan sekadar teori ujian — ini alat praktis sehari-hari: Encapsulation menjaga data tetap aman, Inheritance menghindari duplikasi, Polymorphism membuat kode mudah dikembangkan, dan Abstraction menjaga kompleksitas tetap tersembunyi. Kombinasikan dengan penamaan deskriptif dan method satu-tanggung-jawab, maka kode backend Anda akan rapi, modular, dan mudah dirawat.
Lanjut ke Java Collections Framework untuk belajar mengelola data di memori.