Back to IF5100 Pemrograman untuk Data Analitik

Class Variable dan Operator Overloading Lanjutan (Studi Kasus Rabbit)

Questions/Cues

  • Apa perbedaan class variable dan instance variable, dan bagaimana Rabbit.tag mendemonstrasikan itu?
  • Bagaimana get_rid memakai .zfill() untuk memformat id?
  • Bagaimana cara mengimplementasikan __add__ agar operator + menghasilkan objek Rabbit baru?
  • Mengapa membandingkan objek langsung (self.parent1 == other.parent1) di dalam __eq__ berbahaya, dan bagaimana __eq__ yang benar ditulis?
  • Apa yang sebenarnya “dibeli” OOP secara keseluruhan (dekomposisi, abstraksi, akses konsisten)?

Reference Points

  • IF5100 — Object-Oriented Programming (Slides 39-43)

Class Variable vs Instance Variable

Class variable dan nilainya dibagikan (shared) di antara SEMUA instance dari class tersebut — berbeda dari instance variable yang nilainya unik per objek. Rabbit mendemonstrasikan ini lewat variabel tag:

class Rabbit(Mammal):
    tag = 1
    def __init__(self, age, parent1=None, parent2=None):
        Mammal.__init__(self, age)
        self.parent1 = parent1
        self.parent2 = parent2
        self.rid = Rabbit.tag
        Rabbit.tag += 1

tag adalah class variable — dideklarasikan langsung di bawah class Rabbit(Mammal):, bukan di dalam __init__. Sebaliknya, parent1, parent2, dan rid adalah instance variable biasa (spesifik tiap objek, dibuat lewat self.... di __init__).

self.rid = Rabbit.tag membaca nilai class variable saat ini dan menyimpannya sebagai id unik instance itu. Rabbit.tag += 1 menaikkan class variable — perubahan ini berlaku untuk class itu sendiri, sehingga instance Rabbit berikutnya yang dibuat akan melihat nilai tag yang sudah bertambah. Dengan pola ini, tag berfungsi sebagai penghasil id unik otomatis untuk setiap instance Rabbit baru yang dibuat — mirip auto-increment pada database.

Getter Rabbit dan .zfill()

class Rabbit(Mammal):
    tag = 1
    def __init__(self, age, parent1=None, parent2=None):
        Mammal.__init__(self, age)
        self.parent1 = parent1
        self.parent2 = parent2
        self.rid = Rabbit.tag
        Rabbit.tag += 1
    def get_rid(self):
        return str(self.rid).zfill(3)
    def get_parent1(self):
        return self.parent1
    def get_parent2(self):
        return self.parent2

get_rid memakai method string bawaan .zfill(3) untuk mengisi angka dengan nol di depan hingga panjang 3 digit (mis. "001" alih-alih "1"). Rabbit juga mewarisi get_name/get_age dari Animal (lewat Mammal), sehingga tidak perlu didefinisikan ulang.

Operator + untuk Tipe Sendiri: __add__

def __add__(self, other):
    # returning object of same type as this class
    return Rabbit(0, self, other)

Ini mendefinisikan operator + antara dua instance Rabbit: r4 = r1 + r2 (r1, r2 adalah instance Rabbit). r4 adalah instance Rabbit baru dengan umur 0; r4 memiliki self sebagai satu parent dan other sebagai parent lainnya — mengacu langsung pada signature __init__(self, age, parent1=None, parent2=None) milik Rabbit. Pola ini menunjukkan bagaimana operator overloading bisa membangun objek baru dari kombinasi dua objek lama, mensimulasikan “perkawinan” dua kelinci menghasilkan anak.

Contoh penggunaan + dan get_rid() (keluaran terverifikasi, dimulai dari Rabbit.tag == 1):

r1 = Rabbit(3)
r2 = Rabbit(4)
r3 = r1 + r2                     # Rabbit(0, r1, r2)
print(r1.get_rid(), r2.get_rid())  # 001 002
print(r3.get_rid())              # 003  (id ketiga, berumur 0)
print(r3.get_parent1().get_rid())  # 001  (parent1 adalah r1)
print(Rabbit.tag)                # 4  (nilai tag berikutnya)

str(3).zfill(3) menghasilkan '003'; angka yang sudah 3 digit atau lebih tidak dipotong (str(1234).zfill(3) tetap '1234').

Membandingkan Dua Rabbit: __eq__

Diputuskan bahwa dua kelinci adalah saudara (sibling) jika keduanya memiliki dua parent yang sama:

def __eq__(self, other):
    parents_same = self.parent1.rid == other.parent1.rid \
                    and self.parent2.rid == other.parent2.rid
    parents_opposite = self.parent2.rid == other.parent1.rid \
                        and self.parent1.rid == other.parent2.rid
    return parents_same or parents_opposite

Perbandingan dilakukan lewat id parent (.rid), karena id itu unik (berkat class variable tag). Harus ditangani dua kemungkinan urutan: R1 == R2 dan R2 == R1 harus sama-sama benar terlepas urutan parent1/parent2 disimpan — karena itu kode memeriksa pasangan “sama” (parents_same) dan pasangan “silang/kebalikan” (parents_opposite).

Peringatan penting: kita tidak bisa membandingkan objek Rabbit secara langsung di dalam __eq__, misalnya self.parent1 == other.parent1. Ini akan memicu rekursi tak terkendali — membandingkan dua objek Rabbit dengan == memanggil __eq__ lagi, yang di dalamnya memanggil == lagi pada parent-nya, dan seterusnya — sampai akhirnya __eq__ dipanggil pada nilai None (karena default parent1/parent2 adalah None), menghasilkan AttributeError saat kode mencoba mengakses None.parent1. Itulah sebabnya kode membandingkan .rid (sebuah int) alih-alih membandingkan objek Rabbit secara langsung — int punya __eq__ bawaan yang aman, tidak rekursif ke Rabbit.__eq__.

Penutup: Apa yang Diberikan OOP

OOP memungkinkan kita membuat koleksi data sendiri, mengorganisasi informasi, membagi pekerjaan (divide-and-conquer antar class), mengakses informasi secara konsisten (lewat interface/getter-setter yang seragam), dan menambah lapisan kompleksitas secara bertahap (lewat inheritance). Sebagaimana fungsi, class adalah mekanisme dekomposisi dan abstraksi dalam pemrograman — fungsi mendekomposisi/mengabstraksi perilaku, class mendekomposisi/mengabstraksi data beserta perilakunya sekaligus.

Summary

Class variable (tag, dideklarasikan langsung di badan class) dibagikan di antara semua instance, berbeda dari instance variable (parent1, parent2, rid) yang unik per objek; pola self.rid = Rabbit.tag; Rabbit.tag += 1 memanfaatkan ini untuk menghasilkan id unik otomatis, ditampilkan rapi lewat get_rid yang memakai str(...).zfill(3). __add__ dioverload agar r1 + r2 menghasilkan Rabbit baru dengan age=0 dan kedua operand sebagai parent-nya — operator membangun objek baru dari kombinasi objek lama. __eq__ mendefinisikan dua Rabbit sebagai “saudara” jika pasangan parent-nya sama (dicek dalam kedua urutan lewat parents_same/parents_opposite), dan wajib membandingkan .rid (int), bukan objek Rabbit secara langsung — membandingkan objek langsung memicu rekursi tak terkendali pada __eq__ yang akhirnya menabrak None.parent1 dan melempar AttributeError. Secara keseluruhan, OOP memberi: koleksi data sendiri, organisasi informasi, pembagian kerja (divide-and-conquer), akses konsisten lewat interface, dan lapisan kompleksitas bertahap lewat inheritance — class, seperti fungsi, adalah mekanisme dekomposisi dan abstraksi.