Memahami Konsep Object-Oriented Programming di Python: Dari Class Sampai Polymorphism
Mendefinisikan Class dan Objek sebagai Struktur Data
Object-Oriented Programming atau OOP adalah paradigma yang mengorganisir kode kita ke dalam kumpulan object yang saling berinteraksi. Dengan OOP, kita bisa memodelkan entitas dunia nyata langsung dalam kode, menggabungkan data dan operasi dalam satu kesatuan yang terstruktur. Di Python, hampir semua hal adalah object, termasuk integer, string, dan list. Tapi bagaimana cara kita membuat object kita sendiri?
Jawabannya lewat class. Class adalah definisi tipe data yang menentukan atribut dan method apa yang dimiliki oleh object. Sedangkan object adalah instance konkret dari class tersebut yang menempati memori dan memiliki state spesifik. Kita mendefinisikan class menggunakan keyword class, dan setiap class biasanya memiliki method spesial __init__ yang berfungsi sebagai constructor. Perhatikan parameter self di setiap method instance, ini adalah referensi ke object yang sedang kita proses. self hanyalah konvensi penamaan, bukan keyword khusus; kita bisa menggunakan nama lain, tapi komunitas Python sepakat menggunakan self untuk konsistensi.
class Mahasiswa:
def __init__(self, nama, nilai):
self.nama = nama
self.nilai = nilai
def tampilkan_info(self):
print(f"{self.nama} memiliki nilai {self.nilai}")
# Membuat object dari class Mahasiswa
mhs1 = Mahasiswa("Andi", 85)
mhs2 = Mahasiswa("Budi", 92)
mhs3 = Mahasiswa("Cindy", 78)
mhs1.tampilkan_info()
mhs2.tampilkan_info()
mhs3.tampilkan_info()Output:
Andi memiliki nilai 85
Budi memiliki nilai 92
Cindy memiliki nilai 78Setiap object yang kita buat menyimpan data atributnya sendiri-sendiri. mhs1.nama bernilai "Andi", sedangkan mhs2.nama bernilai "Budi". Kedua object tersebut tidak saling memengaruhi. Inilah esensi dari OOP: menggabungkan data dan perilaku dalam satu kesatuan yang kohesif.

Gambar: Class sebagai blueprint yang mendefinisikan atribut dan method, sedangkan object adalah instance konkret yang menempati memori — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:CPT-OOP-objects_and_classes.svg)
Selain instance attribute, Python juga mendukung class attribute yang nilainya dibagi antar semua instance dari class yang sama. Class attribute didefinisikan langsung di dalam body class, di luar method apapun. Ini berguna untuk menyimpan konstanta atau nilai default yang berlaku untuk seluruh class, misalnya batas maksimum atau label kategori.
Encapsulation dengan Atribut dan Method
Setelah kita punya class, pertanyaan selanjutnya adalah bagaimana mengontrol akses ke data di dalamnya. Di sinilah encapsulation berperan. Encapsulation memungkinkan kita menyembunyikan detail internal class dan hanya mengekspos antarmuka yang aman bagi pengguna class. Python tidak memiliki access modifier seperti private atau protected di Java, tapi ada konvensi yang diikuti oleh para developer.
Atribut tanpa awalan dianggap public. Awalan satu underscore (_) menandakan protected — ini hanya konvensi, bukan aturan ketat. Awalan dua underscore (__) memicu name mangling, membuat Python mengubah nama atribut di belakang layar untuk mengurangi risiko konflik antar class dalam hierarki pewarisan.
Selain itu, Python menyediakan property decorator (@property dan @setter) yang memungkinkan kita membuat getter dan setter dengan sintaks yang elegan. Ini sangat berguna untuk validasi data saat mengakses atau mengubah atribut.
Python juga memiliki dua jenis method lain yang perlu kita pahami. Class method (@classmethod) menerima cls sebagai parameter pertama dan bisa mengakses atau memodifikasi state class secara umum. Ini berguna untuk factory method, yaitu method yang membuat instance class dengan konfigurasi tertentu tanpa harus memanggil constructor secara langsung. Static method (@staticmethod) tidak menerima self maupun cls, berperilaku seperti fungsi biasa yang ditempatkan di dalam class untuk pengorganisasian yang lebih rapi.
class RekeningBank:
def __init__(self, pemilik, saldo_awal=0):
self.pemilik = pemilik
self.__saldo = saldo_awal
@property
def saldo(self):
return self.__saldo
@saldo.setter
def saldo(self, jumlah):
if jumlah < 0:
print("Error: Saldo tidak boleh negatif!")
else:
self.__saldo = jumlah
def setor(self, jumlah):
if jumlah > 0:
self.__saldo += jumlah
print(f"Setor Rp{jumlah}. Saldo: Rp{self.__saldo}")
else:
print("Jumlah setor harus positif.")
# Penggunaan
rek = RekeningBank("Andi", 100000)
print(rek.saldo) # Output: 100000
rek.saldo = 50000 # via setter
print(rek.saldo) # Output: 50000
rek.saldo = -1000 # Error: Saldo tidak boleh negatif!Output:
100000
50000
Error: Saldo tidak boleh negatif!Dengan encapsulation, class kita memiliki antarmuka yang jelas dan data internalnya terlindungi. Ini membuat kode lebih mudah dirawat karena perubahan internal tidak memengaruhi kode pengguna class. Konsumen class cukup menggunakan API publik yang kita sediakan, tanpa perlu tahu detail implementasi di belakangnya.
Inheritance untuk Hierarki Class yang Efisien
Java Fundamental
A hands-on, project-based introduction to Java programming designed for complete...
Salah satu masalah terbesar dalam programming adalah duplikasi kode. Inheritance atau pewarisan membantu kita menghindarinya dengan cara mewariskan atribut dan method dari class induk ke class anak. Ketika beberapa class memiliki perilaku yang sama, kita bisa memindahkan bagian yang umum tersebut ke class induk dan membiarkan class anak mewarisinya tanpa harus menulis ulang kode.
Di Python, sintaksnya sederhana: class Anak(Ortu). Class anak bisa menggunakan semua yang dimiliki class induk, tapi juga bisa menimpa (override) method tertentu dengan implementasinya sendiri. Untuk memanggil constructor atau method class induk, kita gunakan fungsi super(). super() mengembalikan objek proxy yang mewakili class induk, memungkinkan kita memperluas perilaku parent tanpa mengulangi kodenya.
Python juga mendukung multiple inheritance, di mana satu class bisa mewarisi dari lebih dari satu class induk. Ini membawa konsekuensi pada Method Resolution Order (MRO), yaitu urutan Python mencari method ketika dipanggil. Python menggunakan algoritma C3 linearization untuk menentukan urutan ini, memastikan setiap class diwarisi hanya sekali meskipun muncul di beberapa jalur pewarisan. Kita bisa melihat MRO suatu class menggunakan ClassName.__mro__.
class Pegawai:
def __init__(self, nama, gaji_pokok):
self.nama = nama
self.gaji_pokok = gaji_pokok
def hitung_gaji(self):
return self.gaji_pokok
class Dosen(Pegawai):
def __init__(self, nama, gaji_pokok, tunjangan):
super().__init__(nama, gaji_pokok)
self.tunjangan = tunjangan
def hitung_gaji(self): # override method
return self.gaji_pokok + self.tunjangan
class Staff(Pegawai):
def __init__(self, nama, gaji_pokok, lembur):
super().__init__(nama, gaji_pokok)
self.lembur = lembur
def hitung_gaji(self): # override method
return self.gaji_pokok + (self.lembur * 25000)
# Penggunaan
dosen = Dosen("Prof. Andi", 5000000, 2000000)
staff = Staff("Budi", 3500000, 10)
print(f"Gaji {dosen.nama}: Rp{dosen.hitung_gaji()}")
print(f"Gaji {staff.nama}: Rp{staff.hitung_gaji()}")
print(f"MRO Dosen: {Dosen.__mro__}")Output:
Gaji Prof. Andi: Rp7000000
Gaji Budi: Rp3750000
MRO Dosen: (<class '__main__.Dosen'>, <class '__main__.Pegawai'>, <class 'object'>)Perhatikan bagaimana method hitung_gaji() diimplementasikan berbeda di setiap class turunan, tapi tetap konsisten secara antarmuka. Inilah kekuatan inheritance: kita bisa membuat hierarki class yang terstruktur dan efisien. Dengan bantuan fungsi isinstance(), kita juga bisa memeriksa apakah suatu object merupakan instance dari class tertentu, yang berguna dalam validasi tipe di runtime.

Gambar: Ilustrasi inheritance hierarki — class induk (Vehicle) mewariskan atribut ke class anak (Car, Truck) yang bisa memiliki spesialisasi lebih lanjut — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:CPT-OOP-inheritance.svg)
Polymorphism melalui Method Overriding dan Duck Typing
Polymorphism berarti satu antarmuka bisa memiliki banyak bentuk implementasi. Konsep ini memungkinkan kita menulis kode yang bekerja dengan berbagai tipe object selama mereka menyediakan method dengan nama yang sama. Di Python, ini diwujudkan melalui dua cara: method overriding dan duck typing.
Method overriding sudah kita lihat di section sebelumnya. Setiap class anak bisa mengimplementasikan ulang method yang sama dengan perilaku yang berbeda. Yang membuat ini powerful adalah kita bisa memanggil method yang sama pada object dari class yang berbeda, dan masing-masing merespons sesuai implementasinya.
Duck typing adalah pendekatan khas Python yang mengatakan, "Jika sesuatu berjalan seperti bebek dan bersuara seperti bebek, maka itu adalah bebek." Artinya, kita tidak perlu memeriksa tipe object secara eksplisit. Selama object memiliki method yang kita butuhkan, kode akan berjalan. Di balik layar, Python menggunakan protokol seperti __len__ untuk fungsi len() dan __iter__ untuk perulangan for, sehingga tipe data apapun yang mengimplementasikan method ini bisa digunakan dengan fungsi-fungsi bawaan Python.
Python juga menyediakan modul abc (Abstract Base Class) untuk membuat kontrak antarmuka yang lebih ketat. Abstract class tidak bisa diinstansiasi langsung, dan setiap class turunan wajib mengimplementasikan method abstraknya. Ini berguna dalam proyek tim di mana kita perlu memastikan semua developer mengikuti antarmuka yang sudah ditentukan.

Gambar: Polymorphism melalui abstract class — Shape mendeklarasikan method abstrak Area, Rectangle menyediakan implementasi konkretnya — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:UML_abstract_methods.svg)
from abc import ABC, abstractmethod
class Kendaraan(ABC):
@abstractmethod
def bergerak(self):
pass
class Mobil(Kendaraan):
def bergerak(self):
print("Mobil berjalan di jalan raya dengan kecepatan tinggi.")
class Sepeda(Kendaraan):
def bergerak(self):
print("Sepeda dikayuh di jalur khusus.")
class Perahu(Kendaraan):
def bergerak(self):
print("Perahu berlayar di atas air.")
def jalankan_kendaraan(kendaraan):
# Duck typing: kita tidak peduli tipe object-nya
kendaraan.bergerak()
# Semua object bisa diterima selama punya method .bergerak()
jalankan_kendaraan(Mobil())
jalankan_kendaraan(Sepeda())
jalankan_kendaraan(Perahu())Output:
Mobil berjalan di jalan raya dengan kecepatan tinggi.
Sepeda dikayuh di jalur khusus.
Perahu berlayar di atas air.Polymorphism seperti colokan USB: selama perangkatnya menggunakan antarmuka yang sama, kita bisa colok tanpa peduli apa jenis perangkatnya. Di Python, ini membuat kode kita lebih fleksibel dan mudah dikembangkan. Kita bisa menambahkan tipe object baru tanpa mengubah kode yang sudah ada, selama object baru tersebut mengikuti kontrak method yang diharapkan.
Best Practices dalam Merancang Class di Python
Setelah memahami keempat pilar OOP, kita perlu tahu kapan dan bagaimana menerapkannya dengan benar. Berikut beberapa prinsip yang kami gunakan dalam proyek nyata.
Pertama, Composition over Inheritance. Tidak semua hubungan "is-a" harus menggunakan inheritance. Kadang lebih baik menggunakan komposisi, yaitu sebuah class mengandung object dari class lain. Misalnya, class Mobil mengandung object Mesin dan Roda. Komposisi memberikan fleksibilitas yang lebih besar karena kita bisa mengganti komponen secara independen tanpa memengaruhi hierarki class.
Kedua, Single Responsibility Principle. Satu class sebaiknya hanya memiliki satu tanggung jawab. Jika sebuah class mengelola data, validasi, dan juga menangani penyimpanan, itu sudah terlalu banyak. Pisahkan tanggung jawab tersebut ke class yang berbeda. Class dengan satu tanggung jawab lebih mudah dipahami, diuji, dan dimodifikasi tanpa risiko merusak fungsi lainnya.
Ketiga, gunakan method dunder (__str__, __repr__) untuk memberikan representasi string yang informatif pada object kita. __repr__ untuk developer (debugging), __str__ untuk pengguna akhir.
Terakhir, pertimbangkan menggunakan @dataclass untuk class yang terutama berfungsi sebagai penyimpanan data. Decorator ini secara otomatis menghasilkan method __init__, __repr__, __eq__, dan method dasar lainnya berdasarkan atribut yang kita deklarasikan. Ini mengurangi boilerplate code secara signifikan, terutama untuk class data yang sederhana.
from dataclasses import dataclass
# Contoh komposisi
class Mesin:
def __init__(self, tipe):
self.tipe = tipe
def nyalakan(self):
print(f"Mesin {self.tipe} menyala.")
class Roda:
def __init__(self, jumlah):
self.jumlah = jumlah
class Mobil:
def __init__(self, merk, tipe_mesin):
self.merk = merk
self.mesin = Mesin(tipe_mesin)
self.roda = Roda(4)
def start(self):
print(f"{self.merk} siap.")
self.mesin.nyalakan()
# @dataclass untuk class data sederhana
@dataclass
class Alamat:
jalan: str
kota: str
kode_pos: str
def __str__(self):
return f"{self.jalan}, {self.kota} {self.kode_pos}"
mobil = Mobil("Toyota", "VVT-i")
mobil.start()
alamat = Alamat("Jl. Merdeka No.1", "Jakarta", "10110")
print(alamat)Output:
Toyota siap.
Mesin VVT-i menyala.
Jl. Merdeka No.1, Jakarta 10110Dengan menerapkan prinsip-prinsip ini, class yang kita buat akan lebih mudah diuji, dipelihara, dan dikembangkan seiring bertambahnya kompleksitas proyek.
Itulah bahasan lengkap tentang OOP di Python, dari class dan object sampai polymorphism dan best practices. Konsep ini adalah fondasi penting dalam pengembangan aplikasi skala menengah ke atas. Ingin mempraktikkan OOP dalam proyek nyata? Bergabunglah dengan Python Bootcamp di Rumah Coding, di mana kita akan membangun aplikasi berbasis class dan object dari awal sampai deployment.
Kursus Terkait
Java Fundamental
A hands-on, project-based introduction to Java programming designed for complete beginners. Instead of merely memorizing syntax, you will learn to code by building real-world applications from day one. By the end of this course, you will master core programming logic, data structures, object-oriented principles, and debugging techniques, culminating in the development of a fully functional command-line system.
JavaCine: Terminal-Based Movie Ticketing System
- Object-Oriented Movie Catalog: Utilizes a Movie class to encapsulate details like title, genre, duration, and ticket price. The system displays a dynamic list of currently showing films.
- Dynamic Seat Visualization (2D Arrays): Uses a 2D Array to generate a visual seating grid (e.g., 5x5) in the terminal. Available seats are marked as [ O ] and booked seats are marked as [ X ].
- Interactive Booking Engine: A loop-driven menu that allows users to select a movie, choose a specific seat by row and column, and validates the choice. It prevents double-booking if a seat is already taken.
Python Fundamentals
Master the fundamentals of Python through hands-on, real-world projects. Designed for absolute beginners, this course takes you from writing your first line of code to building a fully functional application. By the end of this course, you will have a solid grasp of core programming concepts, data structures, and file management, laying a strong foundation for future studies in Data Science, Web Development, or Automation.
Personal Finance Tracker & Analyzer (CLI)
- Interactive Main Menu: A continuous loop menu allowing users to choose between adding records, viewing summaries, or exiting the app.
- Transaction Logging: Users can input transaction types (Income/Expense), amounts, categories (e.g., Food, Salary, Transport), and descriptions.
- Robust Input Validation: Utilizes try-except blocks to prevent the program from crashing if a user accidentally types letters instead of numbers for financial amounts.