Sitemap
Press enter or click to view image in full size
sumber pinterest : https://pin.it/1AyJPG2J1

OBJECT-ORIENTED PROGRAMMING (OOP)

30 min readJun 4, 2026

--

Dari Akar Filosofis Hingga Praktik Mendalam

BAGIAN 0 — MENGAPA OOP ADA? AKAR HISTORIS DAN FILOSOFIS

Sebelum belajar OOP, kamu harus tahu mengapa ia lahir. Ini bukan sekadar konteks — ini fondasi berpikir yang akan membuat segalanya masuk akal.

0.1 Era Sebelum OOP: Procedural Programming

Di tahun 1950–1960an, program ditulis secara prosedural: kumpulan instruksi berurutan yang dieksekusi dari atas ke bawah. Bahasa seperti FORTRAN, COBOL, dan C menggunakan paradigma ini.

Masalah yang muncul seiring program makin besar:

Masalah 1 — Spaghetti Code: Dengan GOTO statement, alur program melompat ke mana saja, sehingga sulit dilacak.

Masalah 2 — Data dan Fungsi Terpisah: Data (variabel) dan operasi yang memanipulasi data (fungsi) hidup di tempat yang berbeda. Tidak ada cara formal untuk mengatakan “fungsi ini hanya boleh menyentuh data ini.”

Masalah 3 — Skala: Program yang bekerja di 1.000 baris mulai kacau di 10.000 baris, dan hancur di 100.000 baris.

Masalah 4 — Reusability: Sulit menggunakan kembali kode karena fungsi bergantung pada state global yang tidak terdefinisi dengan jelas.

0.2 Kelahiran OOP

Alan Kay, saat mengerjakan Smalltalk di Xerox PARC sekitar 1967–1972, terinspirasi dari biologi sel. Idenya sederhana tapi revolusioner:

“Bayangkan program sebagai kumpulan organisme hidup yang saling berkirim pesan. Setiap organisme menyembunyikan cara kerjanya sendiri, dan mereka berkomunikasi lewat antarmuka yang terdefinisi jelas.”

Kay menyebut unit ini “object” (objek). Ia terinspirasi dari:

  • Biologi: sel yang punya membran (enkapsulasi), bisa berevolusi (inheritance)
  • Fisika: entitas yang punya state dan perilaku
  • Matematika: tipe data abstrak (ADT) dari penelitian Liskov dan Zilles

Bahasa OOP pertama yang berpengaruh:

  • Simula 67 (Ole-Johan Dahl & Kristen Nygaard, 1967) — pertama memperkenalkan class dan object
  • Smalltalk (Alan Kay, 1972) — OOP murni, semua adalah objek
  • C++ (Bjarne Stroustrup, 1983) — OOP di atas C, mainstream
  • Java (James Gosling, Sun Microsystems, 1995) — “write once, run anywhere”
  • Python (Guido van Rossum, 1991) — OOP multi-paradigma

Kata Alan Kay yang sering disalahpahami:

“The big idea is messaging.” — Alan Kay

Kay kemudian menyesal bahwa dunia lebih fokus pada “class” daripada “messaging” (komunikasi antar objek). Ini penting: OOP sejati bukan tentang class, tapi tentang objek yang berkomunikasi.

BAGIAN 1 — KONSEP INTI OOP: EMPAT PILAR

OOP secara tradisional dibangun di atas empat pilar. Tapi memahaminya secara dangkal adalah kesalahan umum. Mari gali sampai akar.

PILAR 1: ENCAPSULATION (ENKAPSULASI)

1.1 Definisi Teknis

Enkapsulasi adalah penggabungan data (atribut/field) dan perilaku (metode/fungsi) ke dalam satu unit (class), sekaligus menyembunyikan detail internal dari dunia luar dan hanya mengekspos antarmuka yang diperlukan.

Ini terdiri dari dua konsep yang sering dikacaukan:

Bundling: Menyatukan data dan metode yang beroperasi pada data tersebut ke dalam satu entitas.

Information Hiding: Menyembunyikan detail implementasi internal. Konsep ini awalnya diperkenalkan oleh David Parnas dalam paper “On the Criteria To Be Used in Decomposing Systems into Modules” (1972).

1.2 Mengapa Information Hiding Itu Krusial?

Bayangkan kamu punya class BankAccount. Tanpa enkapsulasi:

// Tanpa enkapsulasi (pseudocode)

data BankAccount:

balance = 1000

owner = “Budi”

// Di mana saja dalam program:

account.balance = -99999 // Tidak ada yang mencegah ini!

Dengan enkapsulasi:

class BankAccount:

def __init__(self, owner, initial_balance):

self.__owner = owner # private

self.__balance = initial_balance # private

def deposit(self, amount):

if amount <= 0:

raise ValueError(“Jumlah deposit harus positif”)

self.__balance += amount

def withdraw(self, amount):

if amount <= 0:

raise ValueError(“Jumlah penarikan harus positif”)

if amount > self.__balance:

raise ValueError(“Saldo tidak mencukupi”)

self.__balance -= amount

def get_balance(self):

return self.__balance

Sekarang balance tidak bisa diubah sembarangan. Semua perubahan harus melalui deposit() dan withdraw() yang punya validasi.

1.3 Access Modifiers: Mekanisme Enkapsulasi

Access modifier adalah kata kunci yang mengontrol visibilitas member (atribut/metode) sebuah class.

Public: Dapat diakses dari mana saja. Di Java/C++ ditulis public. Di Python, semua atribut public by default (nama biasa: self.name).

Private: Hanya bisa diakses dari dalam class itu sendiri. Di Java/C++: private. Di Python: prefix dua underscore self.__name (name mangling, bukan true private). Di C#: private.

Protected: Bisa diakses dari class itu sendiri dan subclass-nya (class turunannya). Di Java/C++: protected. Di Python: prefix satu underscore self._name (konvensi, tidak di-enforce oleh interpreter).

Package-private / Internal: Di Java, jika tidak ada modifier, member hanya bisa diakses dalam package yang sama. Di C#, internal berarti hanya dalam assembly yang sama.

Tabel ringkasan (Java):

Modifier | Same Class | Same Package | Subclass | Other Package

public | YES | YES | YES | YES

protected | YES | YES | YES | NO

(no modifier) | YES | YES | NO | NO

private | YES | NO | NO | NO

1.4 Getter dan Setter: Bukan Sekedar Boilerplate

Pertanyaan kritis: “Kalau kita buat getter dan setter untuk semua field, apa bedanya dengan public?”

Jawaban: Ada bedanya, tapi getter/setter yang trivial (hanya return/set langsung) adalah code smell.

Getter/setter yang baik:

  • Menambahkan validasi saat set
  • Melakukan transformasi atau komputasi
  • Memberikan read-only access (getter tanpa setter)
  • Memungkinkan lazy initialization
  • Memungkinkan logging atau notifikasi saat perubahan

Getter/setter yang buruk (anemic):

// Ini hampir sama dengan public field, tidak ada nilai tambah

public String getName() { return name; }

public void setName(String name) { this.name = name; }

Getter/setter yang baik:

public void setAge(int age) {

if (age < 0 || age > 150) {

throw new IllegalArgumentException(“Umur tidak valid: “ + age);

}

this.age = age;

}

public String getFullName() {

return firstName + “ “ + lastName; // Komputasi, bukan sekadar return field

}

1.5 Enkapsulasi di Berbagai Bahasa

Python tidak memiliki true private. __name menggunakan name mangling menjadi _ClassName__name, sehingga masih bisa diakses dari luar (dengan nama yang berbeda). Ini adalah filosofi “we are all consenting adults here” — Python percaya programmer dewasa akan menghormati konvensi.

Java memiliki enforcement di level compiler. Mengakses field private dari luar class adalah compile error.

JavaScript (sebelum ES2022) tidak punya private sama sekali. Konvensi _name digunakan. ES2022 memperkenalkan #name sebagai true private field.

C++ memiliki private, public, protected yang di-enforce oleh compiler.

PILAR 2: INHERITANCE (PEWARISAN)

2.1 Definisi dan Mekanisme

Inheritance adalah mekanisme di mana sebuah class (subclass/child class) mewarisi atribut dan metode dari class lain (superclass/parent class). Ini menciptakan hubungan hierarkis “is-a” (adalah sebuah).

Contoh klasik:

class Animal:

def __init__(self, name, sound):

self.name = name

self.sound = sound

def speak(self):

return f”{self.name} berkata {self.sound}”

def breathe(self):

return f”{self.name} bernapas”

class Dog(Animal):

def __init__(self, name):

super().__init__(name, “Guk!”) # memanggil constructor parent

def fetch(self):

return f”{self.name} mengambil bola”

class Cat(Animal):

def __init__(self, name):

super().__init__(name, “Meow!”)

def purr(self):

return f”{self.name} mendengkur”

dog = Dog(“Rex”)

print(dog.speak()) # “Rex berkata Guk!” — diwarisi dari Animal

print(dog.fetch()) # “Rex mengambil bola” — metode sendiri

print(dog.breathe()) # “Rex bernapas” — diwarisi dari Animal

Dog adalah Animal (is-a relationship). Dog mewarisi speak() dan breathe() dari Animal.

2.2 Mengapa Inheritance Ada?

Tujuan utama: Code reuse dan modeling hierarki dunia nyata.

Daripada menulis ulang metode yang sama di Dog, Cat, Bird, kita tulis sekali di Animal dan semua mewarisinya. Ini mengurangi duplikasi.

2.3 super(): Jembatan ke Parent Class

super() adalah cara subclass memanggil metode dari parent class-nya. Ini krusial untuk:

  1. Memanggil constructor parent (sangat umum)
  2. Memperluas (bukan menggantikan) perilaku parent

class Vehicle:

def __init__(self, brand, speed):

self.brand = brand

self.speed = speed

def describe(self):

return f”Kendaraan {self.brand}, kecepatan max {self.speed} km/h”

class Car(Vehicle):

def __init__(self, brand, speed, doors):

super().__init__(brand, speed) # Inisialisasi bagian Vehicle

self.doors = doors # Atribut tambahan milik Car

def describe(self):

base = super().describe() # Dapatkan deskripsi dari parent

return f”{base}, {self.doors} pintu” # Tambahkan informasi Car

car = Car(“Toyota”, 180, 4)

print(car.describe())

# “Kendaraan Toyota, kecepatan max 180 km/h, 4 pintu”

2.4 Method Overriding: Mendefinisikan Ulang Perilaku

Subclass dapat mendefinisikan ulang metode yang diwarisi dari parent. Ini disebut overriding.

class Shape:

def area(self):

return 0 # Default, akan di-override

class Circle(Shape):

def __init__(self, radius):

self.radius = radius

def area(self): # Override metode parent

import math

return math.pi * self.radius ** 2

class Rectangle(Shape):

def __init__(self, width, height):

self.width = width

self.height = height

def area(self): # Override metode parent

return self.width * self.height

shapes = [Circle(5), Rectangle(4, 6)]

for shape in shapes:

print(shape.area())

# 78.539…

# 24

2.5 Single vs Multiple Inheritance

Single Inheritance: Satu class hanya punya satu parent. Ini yang diterapkan di Java (untuk class), C#, Swift.

Multiple Inheritance: Satu class bisa punya lebih dari satu parent. Python, C++ mendukung ini.

class Flyable:

def fly(self):

return “Terbang!”

class Swimmable:

def swim(self):

return “Berenang!”

class Duck(Flyable, Swimmable): # Multiple inheritance

def quack(self):

return “Kwek!”

duck = Duck()

print(duck.fly()) # “Terbang!”

print(duck.swim()) # “Berenang!”

print(duck.quack()) # “Kwek!”

2.6 Diamond Problem: Masalah Fundamental Multiple Inheritance

Ini adalah masalah klasik dalam multiple inheritance:

A

/ \

B C

\ /

D

Jika A punya metode greet(), dan B dan C keduanya meng-override greet(), lalu D mewarisi dari keduanya — metode greet() mana yang digunakan D?

class A:

def greet(self):

return “Halo dari A”

class B(A):

def greet(self):

return “Halo dari B”

class C(A):

def greet(self):

return “Halo dari C”

class D(B, C): # Siapa yang menang?

pass

d = D()

print(d.greet()) # “Halo dari B” — Kenapa?

Python menyelesaikan ini dengan MRO (Method Resolution Order) menggunakan algoritma C3 Linearization. Urutan pencarian metode dapat dilihat dengan:

print(D.__mro__)

# (<class ‘D’>, <class ‘B’>, <class ‘C’>, <class ‘A’>, <class ‘object’>)

Python mencari dari kiri ke kanan, depth-first, dengan memastikan tidak ada class yang diproses sebelum semua subclass-nya.

Java menghindari diamond problem dengan melarang multiple inheritance untuk class (tapi mengizinkannya untuk interface, dengan rules tersendiri sejak Java 8).

2.7 Kritik Terhadap Inheritance: “Inheritance is Evil”

Ini adalah topik debat sengit di komunitas software engineering.

Masalah Inheritance:

Fragile Base Class Problem: Perubahan kecil di parent class bisa merusak semua subclass-nya secara tidak terduga. Parent dan subclass sangat tightly coupled.

Gorilla-Banana Problem (Joe Armstrong): “Kamu meminta pisang, tapi yang kamu dapat adalah gorilla yang memegang pisang — dan seluruh hutan.”
Ketika kamu mewarisi class, kamu mewarisi semua yang dimilikinya, bahkan yang tidak kamu inginkan.

Violation of LSP: Sering kali inheritance dipaksa untuk situasi di mana sebenarnya tidak cocok, melanggar Liskov Substitution Principle (dibahas di bagian SOLID).

Solusi yang direkomendasikan: “Prefer composition over inheritance” — prinsip yang ada di Gang of Four Design Patterns book (1994) dan diperkuat oleh banyak praktisi senior.

PILAR 3: POLYMORPHISM (POLIMORFISME)

3.1 Definisi: Satu Interface, Banyak Bentuk

Polimorfisme berasal dari Yunani: poly (banyak) + morphe (bentuk). Dalam OOP, ini berarti objek dari tipe berbeda dapat diperlakukan sebagai objek dari tipe yang sama, dan perilakunya akan disesuaikan dengan tipe aktualnya.

Ada dua jenis utama polimorfisme:

1. Subtype Polymorphism (Runtime Polymorphism) — Paling umum di OOP

class Animal:

def speak(self):

pass

class Dog(Animal):

def speak(self):

return “Guk!”

class Cat(Animal):

def speak(self):

return “Meow!”

class Bird(Animal):

def speak(self):

return “Cicit!”

# Polimorfisme: semua diperlakukan sebagai Animal

animals = [Dog(), Cat(), Bird()]

for animal in animals:

print(animal.speak()) # Masing-masing memanggil versinya sendiri

# Guk!

# Meow!

# Cicit!

Kunci: animal.speak() dipanggil dengan kode yang sama, tapi perilakunya berbeda tergantung objek aktualnya. Ini disebut dynamic dispatch atau late binding — keputusan metode mana yang dipanggil dibuat saat runtime, bukan compile time.

2. Parametric Polymorphism (Generics/Templates) — Satu kode bekerja untuk berbagai tipe data

// Java Generics

List<String> names = new ArrayList<>();

List<Integer> numbers = new ArrayList<>();

// ArrayList sama, tapi bekerja untuk String dan Integer

# Python: dynamic typing membuat ini alami

def get_first(lst):

return lst[0]

get_first([1, 2, 3]) # Bekerja untuk list int

get_first([“a”, “b”, “c”]) # Bekerja untuk list string

3. Ad-hoc Polymorphism (Method Overloading) — Satu nama fungsi, banyak implementasi berdasarkan tipe parameter

Java mendukung method overloading:

class Calculator {

int add(int a, int b) {

return a + b;

}

double add(double a, double b) {

return a + b;

}

String add(String a, String b) {

return a + b; // String concatenation

}

}

Python tidak punya method overloading tradisional (nama fungsi harus unik), tapi bisa disimulasikan dengan default arguments atau *args.

3.2 Duck Typing: Polimorfisme Tanpa Inheritance

Ini konsep penting di Python (dan bahasa dinamis lainnya).

“If it walks like a duck and quacks like a duck, it must be a duck.”

Dalam duck typing, kamu tidak peduli apa tipe objeknya, yang penting apakah ia punya metode yang kamu butuhkan.

class Duck:

def quack(self):

return “Kwek!”

def walk(self):

return “Jalan seperti bebek”

class Person:

def quack(self):

return “Saya menirukan bebek: Kwek!”

def walk(self):

return “Jalan dengan dua kaki”

def make_it_quack(duck_like):

# Tidak peduli tipenya, asal punya method quack()

print(duck_like.quack())

make_it_quack(Duck()) # “Kwek!”

make_it_quack(Person()) # “Saya menirukan bebek: Kwek!”

Person tidak mewarisi dari Duck, tapi bisa diperlakukan sama karena punya interface yang kompatibel.

3.3 Polymorphism di Level Operator: Operator Overloading

Di Python dan C++, kamu bisa mendefinisikan ulang arti operator (+, -, *, ==, dll.) untuk class-mu sendiri.

class Vector:

def __init__(self, x, y):

self.x = x

self.y = y

def __add__(self, other):

return Vector(self.x + other.x, self.y + other.y)

def __str__(self):

return f”Vector({self.x}, {self.y})”

def __eq__(self, other):

return self.x == other.x and self.y == other.y

v1 = Vector(1, 2)

v2 = Vector(3, 4)

print(v1 + v2) # Vector(4, 6) — menggunakan __add__

print(v1 == v2) # False — menggunakan __eq__

3.4 Virtual Methods dan vtable (Mekanisme Internal)

Di C++, polimorfisme runtime membutuhkan keyword virtual. Tanpa virtual, compiler menggunakan static dispatch (keputusan dibuat saat compile time berdasarkan tipe variabel, bukan tipe objek aktual).

class Animal {

public:

virtual void speak() { // virtual = dynamic dispatch

cout << “…” << endl;

}

};

class Dog : public Animal {

public:

void speak() override { // override keyword (C++11)

cout << “Guk!” << endl;

}

};

Animal* animal = new Dog();

animal->speak(); // “Guk!” — karena virtual

// Tanpa virtual: “…” — static dispatch

Mekanisme internal: Setiap class dengan virtual method punya vtable (virtual table) — tabel pointer ke fungsi-fungsi virtual. Setiap objek punya pointer ke vtable class-nya. Saat memanggil virtual method, runtime melihat vtable untuk menentukan fungsi mana yang dipanggil.

Di Java dan Python, semua method di-dispatch secara dinamis by default (tidak perlu virtual).

PILAR 4: ABSTRACTION (ABSTRAKSI)

4.1 Definisi: Menyembunyikan Kompleksitas

Abstraksi adalah proses menyederhanakan sistem kompleks dengan menyembunyikan detail implementasi dan hanya mengekspos fungsionalitas esensial. Ini tentang apa yang dilakukan sebuah entitas, bukan bagaimana ia melakukannya.

Contoh kehidupan nyata: Ketika kamu menyetir mobil, kamu menggunakan setir, pedal gas, dan rem — kamu tidak perlu tahu bagaimana piston bekerja, bagaimana injeksi bahan bakar terjadi, atau bagaimana ABS menghitung slip roda.

4.2 Abstract Class: Blueprint yang Tidak Bisa Diinstansiasi

Abstract class adalah class yang mengandung setidaknya satu abstract method — metode yang dideklarasikan tapi tidak diimplementasikan. Abstract class tidak bisa dibuat instance-nya langsung; ia harus di-subclass, dan subclass harus mengimplementasikan semua abstract method.

Python (menggunakan ABC module):

from abc import ABC, abstractmethod

class Shape(ABC): # Abstract class

@abstractmethod

def area(self) -> float:

“””Menghitung luas shape”””

pass

@abstractmethod

def perimeter(self) -> float:

“””Menghitung keliling shape”””

pass

def describe(self): # Metode konkret (non-abstract)

return f”Luas: {self.area()}, Keliling: {self.perimeter()}”

class Circle(Shape):

def __init__(self, radius):

self.radius = radius

def area(self): # Harus diimplementasikan

import math

return math.pi * self.radius ** 2

def perimeter(self): # Harus diimplementasikan

import math

return 2 * math.pi * self.radius

# shape = Shape() # ERROR: Can’t instantiate abstract class

circle = Circle(5)

print(circle.describe()) # Bisa, karena diwarisi dari Shape

Java:

public abstract class Shape {

public abstract double area(); // Abstract method

public abstract double perimeter(); // Abstract method

public void describe() { // Concrete method

System.out.println(“Luas: “ + area());

}

}

public class Rectangle extends Shape {

private double width, height;

public Rectangle(double width, double height) {

this.width = width;

this.height = height;

}

@Override

public double area() { return width * height; }

@Override

public double perimeter() { return 2 * (width + height); }

}

4.3 Interface: Kontrak Murni

Interface adalah “kontrak” yang mendefinisikan apa yang harus bisa dilakukan sebuah class, tanpa mendefinisikan bagaimana. Interface tidak punya implementasi (sebelum Java 8 / default methods).

Perbedaan Abstract Class vs Interface — ini salah satu pertanyaan paling sering ditanyakan:

Abstract Class:

  • Bisa punya atribut (state)
  • Bisa punya metode dengan implementasi
  • Bisa punya constructor
  • Subclass hanya bisa inherit SATU abstract class (single inheritance)
  • Digunakan ketika ada hubungan “is-a” yang kuat dan ada state/behavior yang ingin dibagi

Interface:

  • Tidak punya state (atribut) — hanya konstanta (di Java)
  • Tidak punya implementasi (kecuali default methods di Java 8+)
  • Tidak punya constructor
  • Satu class bisa implement BANYAK interface
  • Digunakan untuk mendefinisikan “kemampuan” (capability) yang bisa dimiliki oleh class yang tidak terkait sekalipun

// Interface = kontrak kemampuan

public interface Flyable {

void fly();

default void land() { // Default method (Java 8+)

System.out.println(“Mendarat…”);

}

}

public interface Swimmable {

void swim();

}

// Class tidak terkait bisa implement interface yang sama

public class Duck implements Flyable, Swimmable {

public void fly() { System.out.println(“Bebek terbang”); }

public void swim() { System.out.println(“Bebek berenang”); }

}

public class Airplane implements Flyable {

public void fly() { System.out.println(“Pesawat terbang”); }

}

4.4 Kapan Pakai Abstract Class vs Interface?

Gunakan abstract class ketika:

  • Kamu ingin berbagi kode (implementasi) di antara beberapa class terkait erat
  • Subclass-mu akan punya banyak metode dan field yang sama
  • Kamu ingin bisa mendeklarasikan field non-static atau non-final
  • Ada hubungan hierarkis yang jelas (Animal → Mammal → Dog)

Gunakan interface ketika:

  • Kamu ingin mendefinisikan perilaku yang bisa diimplementasikan oleh class-class yang tidak terkait
  • Kamu ingin memanfaatkan “multiple inheritance of type”
  • Kamu ingin menetapkan kontrak tanpa peduli implementasinya
  • Contoh: Comparable, Serializable, Runnable di Java

BAGIAN 2 — CLASS DAN OBJECT: LEBIH DALAM

2.1 Class vs Object: Perbedaan Fundamental

Class adalah cetak biru (blueprint), template, atau definisi. Ia mendeskripsikan struktur dan perilaku yang akan dimiliki oleh setiap object yang dibuat darinya.

Object (juga disebut instance) adalah realisasi konkret dari class tersebut di memori. Setiap object punya identity unik, state sendiri, dan perilaku sesuai class-nya.

Analogi:

  • Class = Resep kue (mendefinisikan bahan dan cara membuat)
  • Object = Kue yang jadi (nyata, bisa dimakan, punya bentuk dan rasa)

class Car:

# Atribut class (dibagi semua instance)

total_cars_created = 0

def __init__(self, brand, color, year):

# Atribut instance (unik per object)

self.brand = brand

self.color = color

self.year = year

self.speed = 0

Car.total_cars_created += 1

def accelerate(self, amount):

self.speed += amount

def brake(self, amount):

self.speed = max(0, self.speed — amount)

# Object adalah instance dari class

car1 = Car(“Toyota”, “Merah”, 2020)

car2 = Car(“Honda”, “Hitam”, 2022)

# Masing-masing punya state sendiri

car1.accelerate(60)

print(car1.speed) # 60

print(car2.speed) # 0 — tidak terpengaruh

# Tapi berbagi class attribute

print(Car.total_cars_created) # 2

2.2 Constructor dan Destructor

Constructor adalah metode khusus yang dipanggil secara otomatis saat object dibuat. Tujuannya adalah menginisialisasi state awal object.

class DatabaseConnection:

def __init__(self, host, port, database):

# Ini constructor

self.host = host

self.port = port

self.database = database

self.connection = self._connect()

print(f”Koneksi dibuat ke {host}:{port}/{database}”)

def _connect(self):

# Simulasi koneksi

return f”connection_handle_{self.host}”

def __del__(self):

# Ini destructor — dipanggil saat object dihapus

print(f”Koneksi ke {self.host} ditutup”)

db = DatabaseConnection(“localhost”, 5432, “mydb”)

# Output: “Koneksi dibuat ke localhost:5432/mydb”

del db

# Output: “Koneksi ke localhost ditutup”

Catatan penting tentang destructor di Python: __del__ tidak dijamin dipanggil segera saat del dipanggil, atau bahkan saat program berakhir dalam semua kasus. Untuk resource management yang reliable, gunakan Context Manager (protocol __enter__ dan __exit__ dengan keyword with).

Constructor Overloading: Di Python, tidak ada constructor overloading langsung. Gunakan default parameters atau class methods sebagai alternative constructors:

class Date:

def __init__(self, year, month, day):

self.year = year

self.month = month

self.day = day

@classmethod

def from_string(cls, date_string):

# Alternative constructor: membuat Date dari string “2024–01–15”

year, month, day = map(int, date_string.split(‘-’))

return cls(year, month, day)

@classmethod

def today(cls):

# Alternative constructor: membuat Date dari hari ini

from datetime import date

d = date.today()

return cls(d.year, d.month, d.day)

d1 = Date(2024, 1, 15) # Constructor biasa

d2 = Date.from_string(“2024–01–15”) # Alternative constructor

d3 = Date.today() # Alternative constructor

2.3 Instance Methods, Class Methods, Static Methods

Tiga jenis metode yang sering membingungkan:

Instance Method: Metode biasa. Menerima self sebagai parameter pertama. Bisa mengakses atribut instance (self.x) dan class (ClassName.x). Ini yang paling umum.

Class Method: Didekorasi dengan @classmethod. Menerima cls (class itu sendiri) sebagai parameter pertama, bukan instance. Digunakan untuk factory methods atau operasi yang berkaitan dengan class, bukan instance tertentu.

Static Method: Didekorasi dengan @staticmethod. Tidak menerima self atau cls. Secara teknis tidak terikat ke instance atau class — ini hanyalah fungsi biasa yang diletakkan di dalam namespace class karena secara logis berkaitan.

class MathHelper:

pi = 3.14159

def __init__(self, value):

self.value = value

def double(self): # Instance method

return self.value * 2

@classmethod

def circle_area(cls, radius): # Class method — akses ke cls.pi

return cls.pi * radius ** 2

@staticmethod

def is_even(number): # Static method — tidak perlu self atau cls

return number % 2 == 0

helper = MathHelper(10)

print(helper.double()) # 20 (instance method)

print(MathHelper.circle_area(5)) # 78.539… (class method)

print(MathHelper.is_even(4)) # True (static method)

print(helper.is_even(4)) # True (bisa dipanggil via instance, tapi tidak ideal)

2.4 Class Attributes vs Instance Attributes

Class attributes didefinisikan di level class (di luar __init__). Dibagi oleh semua instance.

Instance attributes didefinisikan di dalam __init__ (atau metode lain) menggunakan self.. Unik per instance.

class Student:

school_name = “SMA Negeri 1” # Class attribute — dibagi semua

student_count = 0

def __init__(self, name, grade):

self.name = name # Instance attribute — unik per student

self.grade = grade

Student.student_count += 1

s1 = Student(“Budi”, 10)

s2 = Student(“Ani”, 11)

print(s1.school_name) # “SMA Negeri 1”

print(s2.school_name) # “SMA Negeri 1” — sama

Student.school_name = “SMA Negeri 2” # Ubah class attribute

print(s1.school_name) # “SMA Negeri 2” — berubah!

print(s2.school_name) # “SMA Negeri 2” — berubah!

print(Student.student_count) # 2

Peringatan penting: Jika kamu melakukan s1.school_name = “lain”, ini tidak mengubah class attribute — ini membuat instance attribute baru di s1 yang menutupi (shadows) class attribute. Class attribute dan s2 tidak terpengaruh.

BAGIAN 3 — PRINSIP-PRINSIP SOLID

SOLID adalah singkatan dari lima prinsip desain OOP yang dirumuskan oleh Robert C. Martin (“Uncle Bob”). Ini adalah panduan untuk membuat kode yang maintainable, flexible, dan mudah dipahami.

S — Single Responsibility Principle (SRP)

Prinsip: Sebuah class harus punya satu, dan hanya satu, alasan untuk berubah. Dengan kata lain, satu class harus punya satu tanggung jawab.

Mengapa? Jika sebuah class punya banyak tanggung jawab, perubahan pada satu tanggung jawab bisa merusak yang lain.

Buruk (violasi SRP):

class Employee:

def __init__(self, name, salary):

self.name = name

self.salary = salary

def calculate_pay(self):

return self.salary * 1.1 # Logika bisnis

def save_to_database(self):

# Query SQL langsung di sini — tanggung jawab persistence

print(f”INSERT INTO employees VALUES (‘{self.name}’, {self.salary})”)

def generate_report(self):

# Logika report — tanggung jawab presentasi

return f”Laporan untuk {self.name}: gaji {self.salary}”

Employee punya tiga tanggung jawab: logika bisnis, persistensi data, presentasi.

Baik (mengikuti SRP):

class Employee:

def __init__(self, name, salary):

self.name = name

self.salary = salary

def calculate_pay(self):

return self.salary * 1.1

class EmployeeRepository:

def save(self, employee):

print(f”INSERT INTO employees VALUES (‘{employee.name}’)”)

class EmployeeReportGenerator:

def generate(self, employee):

return f”Laporan untuk {employee.name}”

O — Open/Closed Principle (OCP)

Prinsip: Software entities (class, module, fungsi) harus terbuka untuk ekstensi tapi tertutup untuk modifikasi. Artinya, kamu bisa menambahkan fungsionalitas baru tanpa mengubah kode yang sudah ada.

Buruk (violasi OCP):

class AreaCalculator:

def calculate(self, shapes):

total = 0

for shape in shapes:

if isinstance(shape, dict) and shape[‘type’] == ‘circle’:

import math

total += math.pi * shape[‘radius’] ** 2

elif isinstance(shape, dict) and shape[‘type’] == ‘rectangle’:

total += shape[‘width’] * shape[‘height’]

# Setiap tambah shape baru = harus modifikasi class ini!

return total

Baik (mengikuti OCP):

from abc import ABC, abstractmethod

import math

class Shape(ABC):

@abstractmethod

def area(self) -> float:

pass

class Circle(Shape):

def __init__(self, radius):

self.radius = radius

def area(self):

return math.pi * self.radius ** 2

class Rectangle(Shape):

def __init__(self, width, height):

self.width = width

self.height = height

def area(self):

return self.width * self.height

class Triangle(Shape): # Tambah shape baru tanpa modifikasi AreaCalculator!

def __init__(self, base, height):

self.base = base

self.height = height

def area(self):

return 0.5 * self.base * self.height

class AreaCalculator:

def calculate(self, shapes):

return sum(shape.area() for shape in shapes)

# Kode ini tidak perlu diubah untuk shape baru apapun

L — Liskov Substitution Principle (LSP)

Prinsip: Objek dari subtype harus bisa menggantikan objek dari supertypenya tanpa merusak kebenaran program. Dinamai dari Barbara Liskov yang pertama kali menyatakannya pada 1987.

Formal: Jika S adalah subtype dari T, maka objek tipe T dalam suatu program bisa diganti dengan objek tipe S tanpa mengubah sifat-sifat yang diinginkan dari program tersebut.

Contoh violasi LSP yang terkenal — Rectangle dan Square:

Secara matematika, persegi (square) adalah persegi panjang (rectangle) khusus. Tapi dalam OOP:

class Rectangle:

def __init__(self, width, height):

self._width = width

self._height = height

@property

def width(self):

return self._width

@width.setter

def width(self, value):

self._width = value

@property

def height(self):

return self._height

@height.setter

def height(self, value):

self._height = value

def area(self):

return self._width * self._height

class Square(Rectangle):

@Rectangle.width.setter

def width(self, value):

self._width = value

self._height = value # Square selalu sama lebar dan tinggi

@Rectangle.height.setter

def height(self, value):

self._width = value

self._height = value

# Fungsi yang bekerja dengan Rectangle

def test_rectangle(rect):

rect.width = 5

rect.height = 10

assert rect.area() == 50, f”Area seharusnya 50, tapi {rect.area()}”

print(f”Area: {rect.area()} ✓”)

r = Rectangle(2, 3)

test_rectangle(r) # OK: Area 50

s = Square(5)

Get Dadan Satria’s stories in your inbox

Join Medium for free to get updates from this writer.

test_rectangle(s) # GAGAL! Area = 100, bukan 50

# Karena set height=10 juga set width=10

Square tidak bisa menggantikan Rectangle karena perilakunya berbeda. Ini violasi LSP.

Solusi: Square dan Rectangle tidak harus dalam hubungan inheritance. Keduanya bisa implement interface Shape yang sama tapi independent.

I — Interface Segregation Principle (ISP)

Prinsip: Client tidak harus bergantung pada interface yang tidak mereka gunakan. Lebih baik banyak interface spesifik daripada satu interface besar yang general.

Buruk (violasi ISP):

from abc import ABC, abstractmethod

class Machine(ABC): # Interface terlalu besar

@abstractmethod

def print(self): pass

@abstractmethod

def scan(self): pass

@abstractmethod

def fax(self): pass

@abstractmethod

def copy(self): pass

class SimplePrinter(Machine):

def print(self): print(“Mencetak…”)

def scan(self):

raise NotImplementedError(“Printer ini tidak bisa scan!”) # Terpaksa

def fax(self):

raise NotImplementedError(“Printer ini tidak bisa fax!”) # Tidak perlu

def copy(self):

raise NotImplementedError(“Printer ini tidak bisa copy!”) # Tidak perlu

Baik (mengikuti ISP):

from abc import ABC, abstractmethod

class Printable(ABC):

@abstractmethod

def print(self): pass

class Scannable(ABC):

@abstractmethod

def scan(self): pass

class Faxable(ABC):

@abstractmethod

def fax(self): pass

class SimplePrinter(Printable):

def print(self): print(“Mencetak…”)

class MultiFunctionPrinter(Printable, Scannable, Faxable):

def print(self): print(“Mencetak…”)

def scan(self): print(“Scanning…”)

def fax(self): print(“Mengirim fax…”)

D — Dependency Inversion Principle (DIP)

Prinsip:

  1. High-level modules tidak boleh bergantung pada low-level modules. Keduanya harus bergantung pada abstraksi.
  2. Abstraksi tidak boleh bergantung pada detail. Detail harus bergantung pada abstraksi.

Buruk (violasi DIP):

class MySQLDatabase: # Low-level module

def save(self, data):

print(f”Menyimpan ke MySQL: {data}”)

class UserService: # High-level module — bergantung langsung ke low-level

def __init__(self):

self.db = MySQLDatabase() # Tightly coupled!

def create_user(self, user_data):

# Logika bisnis…

self.db.save(user_data)

Jika kita ingin mengganti MySQLDatabase dengan PostgreSQLDatabase, kita harus mengubah UserService.

Baik (mengikuti DIP):

from abc import ABC, abstractmethod

class Database(ABC): # Abstraksi (interface)

@abstractmethod

def save(self, data): pass

class MySQLDatabase(Database): # Low-level module bergantung pada abstraksi

def save(self, data):

print(f”Menyimpan ke MySQL: {data}”)

class PostgreSQLDatabase(Database):

def save(self, data):

print(f”Menyimpan ke PostgreSQL: {data}”)

class UserService: # High-level module bergantung pada abstraksi

def __init__(self, database: Database): # Dependency injection

self.db = database

def create_user(self, user_data):

self.db.save(user_data)

# Fleksibel — bisa ganti database tanpa ubah UserService

service1 = UserService(MySQLDatabase())

service2 = UserService(PostgreSQLDatabase())

Ini adalah dasar dari Dependency Injection (DI) — pola yang sangat populer di enterprise programming.

BAGIAN 4 — COMPOSITION VS INHERITANCE

Ini salah satu topik paling penting dan sering diabaikan.

4.1 Prinsip Composition Over Inheritance

Dari buku “Design Patterns: Elements of Reusable Object-Oriented Software” (Gang of Four, 1994):

“Favor object composition over class inheritance.”

Inheritance menciptakan hubungan is-a yang kuat dan statis. Hubungan ini di-encode pada compile time dan sulit diubah.

Composition menciptakan hubungan has-a yang lebih fleksibel. Objek mengandung instance dari objek lain dan mendelegasikan pekerjaan kepadanya. Hubungan ini bisa diubah di runtime.

4.2 Masalah dengan Inheritance yang Berlebihan

# Hierarchy inheritance yang terlalu dalam — masalah nyata

class Animal:

pass

class Vertebrate(Animal):

pass

class Mammal(Vertebrate):

pass

class Pet(Mammal):

pass

class Dog(Pet):

pass

class TrainedDog(Dog):

pass

class ServiceDog(TrainedDog):

pass

Hierarchy 7 level ke bawah. Perubahan di mana saja bisa merusak level di bawahnya. Sulit dipahami, sulit di-test.

4.3 Composition sebagai Alternatif

# Dengan composition

class Breathable:

def breathe(self):

return “Bernapas”

class Trainable:

def __init__(self):

self.tricks = []

def learn_trick(self, trick):

self.tricks.append(trick)

def perform_tricks(self):

return f”Melakukan: {‘, ‘.join(self.tricks)}”

class Pet:

def __init__(self, name, owner):

self.name = name

self.owner = owner

def get_owner(self):

return self.owner

class ServiceCapability:

def __init__(self, service_type):

self.service_type = service_type

def perform_service(self):

return f”Melakukan layanan: {self.service_type}”

class Dog:

def __init__(self, name, owner, service_type=None):

self.name = name

self.breathable = Breathable()

self.pet = Pet(name, owner)

self.trainable = Trainable()

if service_type:

self.service = ServiceCapability(service_type)

else:

self.service = None

def breathe(self):

return self.breathable.breathe()

def learn(self, trick):

self.trainable.learn_trick(trick)

def perform(self):

return self.trainable.perform_tricks()

dog = Dog(“Rex”, “Budi”, service_type=”Guide Dog”)

dog.learn(“duduk”)

dog.learn(“rebahan”)

print(dog.perform()) # “Melakukan: duduk, rebahan”

Sekarang perilaku bisa dikombinasikan secara fleksibel tanpa hierarchy yang dalam.

4.4 Mixin: Jalan Tengah

Mixin adalah class yang memberikan fungsionalitas tambahan kepada class lain melalui inheritance, tapi tidak dimaksudkan untuk berdiri sendiri sebagai entitas utama.

class JSONSerializableMixin:

def to_json(self):

import json

return json.dumps(self.__dict__)

@classmethod

def from_json(cls, json_string):

import json

data = json.loads(json_string)

return cls(**data)

class LoggableMixin:

def log(self, message):

import datetime

timestamp = datetime.datetime.now().isoformat()

print(f”[{timestamp}] {self.__class__.__name__}: {message}”)

class User(JSONSerializableMixin, LoggableMixin):

def __init__(self, name, email):

self.name = name

self.email = email

user = User(“Budi”, “budi@email.com”)

print(user.to_json()) # ‘{“name”: “Budi”, “email”: “budi@email.com”}’

user.log(“User dibuat”) # “[2024-…] User: User dibuat”

Mixin tidak merepresentasikan konsep domain (bukan “sesuatu”), mereka hanya menambahkan kapabilitas.

BAGIAN 5 — DESIGN PATTERNS OOP

Design patterns adalah solusi yang sudah terbukti untuk masalah desain yang berulang. Diperkenalkan oleh Gang of Four (GoF) dalam buku “Design Patterns” (1994): Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.

Ada 23 pattern GoF, dibagi tiga kategori: Creational, Structural, Behavioral.

5.1 Creational Patterns

Singleton

Memastikan bahwa sebuah class hanya punya satu instance, dan menyediakan akses global ke instance tersebut.

class DatabasePool:

_instance = None

def __new__(cls):

if cls._instance is None:

cls._instance = super().__new__(cls)

cls._instance.connections = []

print(“DatabasePool dibuat (hanya sekali)”)

return cls._instance

def add_connection(self, conn):

self.connections.append(conn)

pool1 = DatabasePool()

pool2 = DatabasePool()

print(pool1 is pool2) # True — objek yang sama!

pool1.add_connection(“conn1”)

print(pool2.connections) # [‘conn1’] — karena pool1 dan pool2 adalah objek yang sama

Peringatan: Singleton sering dianggap “anti-pattern” karena membuat global state yang tersembunyi dan sulit di-test. Gunakan dengan hati-hati.

Factory Method

Mendefinisikan interface untuk membuat objek, tapi membiarkan subclass memutuskan class mana yang akan diinstansiasi.

from abc import ABC, abstractmethod

class Notification(ABC):

@abstractmethod

def send(self, message): pass

class EmailNotification(Notification):

def send(self, message):

print(f”Mengirim email: {message}”)

class SMSNotification(Notification):

def send(self, message):

print(f”Mengirim SMS: {message}”)

class PushNotification(Notification):

def send(self, message):

print(f”Mengirim push: {message}”)

def notification_factory(notif_type: str) -> Notification:

factories = {

‘email’: EmailNotification,

‘sms’: SMSNotification,

‘push’: PushNotification

}

if notif_type not in factories:

raise ValueError(f”Tipe notifikasi tidak dikenal: {notif_type}”)

return factories[notif_type]()

notif = notification_factory(‘email’)

notif.send(“Selamat datang!”) # “Mengirim email: Selamat datang!”

5.2 Structural Patterns

Decorator

Menambahkan tanggung jawab baru ke objek secara dinamis, tanpa mengubah class aslinya.

from abc import ABC, abstractmethod

class Coffee(ABC):

@abstractmethod

def cost(self) -> float: pass

@abstractmethod

def description(self) -> str: pass

class SimpleCoffee(Coffee):

def cost(self): return 10000

def description(self): return “Kopi hitam”

class CoffeeDecorator(Coffee, ABC):

def __init__(self, coffee: Coffee):

self._coffee = coffee

def cost(self): return self._coffee.cost()

def description(self): return self._coffee.description()

class MilkDecorator(CoffeeDecorator):

def cost(self): return self._coffee.cost() + 3000

def description(self): return self._coffee.description() + “, susu”

class SugarDecorator(CoffeeDecorator):

def cost(self): return self._coffee.cost() + 1000

def description(self): return self._coffee.description() + “, gula”

class WhipDecorator(CoffeeDecorator):

def cost(self): return self._coffee.cost() + 5000

def description(self): return self._coffee.description() + “, whip cream”

# Kombinasi fleksibel

coffee = SimpleCoffee()

coffee = MilkDecorator(coffee)

coffee = SugarDecorator(coffee)

coffee = WhipDecorator(coffee)

print(coffee.description()) # “Kopi hitam, susu, gula, whip cream”

print(coffee.cost()) # 19000

Python memiliki built-in support untuk decorator pattern melalui syntax @decorator.

5.3 Behavioral Patterns

Observer

Mendefinisikan dependensi one-to-many antara objek. Ketika satu objek berubah state, semua dependan-nya diberitahu dan diupdate otomatis.

from abc import ABC, abstractmethod

from typing import List

class Observer(ABC):

@abstractmethod

def update(self, event: str, data): pass

class Subject:

def __init__(self):

self._observers: List[Observer] = []

def subscribe(self, observer: Observer):

self._observers.append(observer)

def unsubscribe(self, observer: Observer):

self._observers.remove(observer)

def notify(self, event: str, data=None):

for observer in self._observers:

observer.update(event, data)

class EventLogger(Observer):

def update(self, event, data):

print(f”LOG: Event ‘{event}’ terjadi dengan data: {data}”)

class EmailService(Observer):

def update(self, event, data):

if event == “user_registered”:

print(f”EMAIL: Selamat datang, {data[‘name’]}!”)

class UserService(Subject):

def register_user(self, name, email):

user = {“name”: name, “email”: email}

# Simpan ke database…

self.notify(“user_registered”, user)

return user

service = UserService()

service.subscribe(EventLogger())

service.subscribe(EmailService())

service.register_user(“Budi”, “budi@email.com”)

# LOG: Event ‘user_registered’ terjadi dengan data: {‘name’: ‘Budi’, …}

# EMAIL: Selamat datang, Budi!

Strategy

Mendefinisikan keluarga algoritma, mengenkapsulasi masing-masing, dan membuat mereka dapat dipertukarkan.

from abc import ABC, abstractmethod

class SortStrategy(ABC):

@abstractmethod

def sort(self, data: list) -> list: pass

class BubbleSort(SortStrategy):

def sort(self, data):

d = data.copy()

n = len(d)

for i in range(n):

for j in range(0, n-i-1):

if d[j] > d[j+1]:

d[j], d[j+1] = d[j+1], d[j]

return d

class QuickSort(SortStrategy):

def sort(self, data):

if len(data) <= 1:

return data

pivot = data[len(data) // 2]

left = [x for x in data if x < pivot]

middle = [x for x in data if x == pivot]

right = [x for x in data if x > pivot]

return self.sort(left) + middle + self.sort(right)

class Sorter:

def __init__(self, strategy: SortStrategy):

self._strategy = strategy

def set_strategy(self, strategy: SortStrategy):

self._strategy = strategy

def sort(self, data):

return self._strategy.sort(data)

data = [5, 2, 8, 1, 9, 3]

sorter = Sorter(BubbleSort())

print(sorter.sort(data)) # [1, 2, 3, 5, 8, 9]

sorter.set_strategy(QuickSort()) # Ganti algoritma di runtime!

print(sorter.sort(data)) # [1, 2, 3, 5, 8, 9]

BAGIAN 6 — OOP DI BERBAGAI BAHASA: PERBEDAAN KRITIS

6.1 Python

Python adalah bahasa multi-paradigma dengan OOP yang fleksibel tapi berbeda dari Java/C++.

  • Semua adalah objek, termasuk integer, string, fungsi, bahkan class itu sendiri
  • Dynamic typing: tidak perlu deklarasi tipe
  • No true private (name mangling bukan enforcement)
  • Multiple inheritance dengan C3 linearization
  • Duck typing dan Protocol (PEP 544, Python 3.8+) sebagai alternatif interface formal
  • Dunder methods (__init__, __str__, __len__, dll.) untuk operator overloading dan protokol
  • @property untuk getter/setter yang elegan
  • Metaclass untuk mengontrol pembuatan class itu sendiri

Dokumentasi resmi Python OOP: https://docs.python.org/3/tutorial/classes.html

6.2 Java

Java adalah bahasa OOP “pure” di mana hampir segalanya adalah object (kecuali primitive types: int, double, boolean, dll.).

  • Static typing: tipe harus dideklarasikan
  • Single inheritance untuk class, multiple inheritance untuk interface
  • True private, protected, public, package-private
  • interface dan abstract class sebagai konsep formal
  • Generics untuk parametric polymorphism
  • final untuk mencegah inheritance atau overriding
  • Automatic memory management (Garbage Collector)
  • Java 8+ menambahkan: default methods di interface, lambda, streams

Dokumentasi resmi Java OOP: https://docs.oracle.com/javase/tutorial/java/concepts/

6.3 JavaScript/TypeScript

JavaScript adalah bahasa berbasis prototipe (prototype-based), bukan class-based. Class di JavaScript (ES6+) adalah syntactic sugar di atas prototype chain.

// Class syntax (ES6+) — ini adalah syntactic sugar

class Animal {

#name; // Private field (ES2022)

constructor(name) {

this.#name = name;

}

speak() {

return `${this.#name} berbicara`;

}

}

// Di bawah hood, ini adalah prototype chain:

function Animal(name) {

this._name = name;

}

Animal.prototype.speak = function() {

return `${this._name} berbicara`;

};

TypeScript menambahkan static typing dan fitur OOP yang lebih lengkap di atas JavaScript. Dokumentasi TypeScript: https://www.typescriptlang.org/docs/handbook/2/classes.html

6.4 C++

C++ adalah bahasa multi-paradigma yang memberikan kontrol sangat detail.

  • Multiple inheritance penuh
  • Manual memory management (new/delete) — tanggung jawab programmer
  • Smart pointers (unique_ptr, shared_ptr) untuk memory management yang lebih aman
  • Virtual functions dan vtable untuk polymorphism
  • Templates untuk generic programming (lebih powerful dari Java Generics)
  • Friend functions dan classes untuk akses khusus
  • Operator overloading yang sangat fleksibel

Dokumentasi referensi C++: https://en.cppreference.com/w/cpp/language/classes

BAGIAN 7 — MEMORY MANAGEMENT DALAM OOP

7.1 Heap vs Stack

Dalam kebanyakan bahasa, ada dua area memori utama:

Stack: Memori yang dikelola secara otomatis, LIFO (Last In First Out). Untuk variabel lokal dan pemanggilan fungsi. Alokasi dan dealokasi cepat. Ukuran terbatas.

Heap: Memori dinamis yang bisa dialokasikan sewaktu-waktu. Objek (instances dari class) biasanya disimpan di heap. Lebih lambat dari stack. Perlu dikelola (manual atau automatic).

Di Python, Java, C#: Semua objek di heap, memory dikelola oleh Garbage Collector. Di C++: Programmer mengontrol sendiri dengan new (alokasi di heap) dan delete (dealokasi).

7.2 Garbage Collection

Garbage Collector (GC) secara otomatis membebaskan memori yang tidak lagi digunakan (tidak ada referensi yang menuju ke objek tersebut).

Python menggunakan reference counting sebagai mekanisme utama: setiap objek punya counter berisi jumlah referensi yang menunjuk ke objek tersebut. Ketika counter mencapai 0, objek langsung dihapus. Python juga punya cycle detector untuk menangani circular references.

import sys

x = [1, 2, 3]

print(sys.getrefcount(x)) # Menampilkan jumlah referensi

y = x # Referensi tambahan

print(sys.getrefcount(x)) # Meningkat

del y # Kurangi referensi

# x masih ada karena masih ada referensi dari x

del x # Counter = 0, objek dihapus dari memori

Java menggunakan berbagai algoritma GC (Mark-and-Sweep, G1GC, ZGC, dll.) yang berjalan di background thread. GC memindai semua objek yang masih bisa dijangkau dari “root references” dan membebaskan yang tidak terjangkau.

7.3 Memory Leaks dalam OOP

Memory leak terjadi ketika program mengalokasikan memori tapi tidak pernah membebaskannya, menyebabkan konsumsi memori terus bertumbuh.

Dalam bahasa dengan GC, memory leak masih bisa terjadi jika ada referensi yang tidak sengaja dipertahankan:

# Contoh memory leak di Python

class Cache:

_items = [] # Class attribute — hidup selama program berjalan

@classmethod

def add(cls, item):

cls._items.append(item) # Terus bertambah, tidak pernah dibersihkan

Solusi: Gunakan weakref untuk referensi yang tidak mencegah garbage collection, atau aktif mengelola lifecycle objek.

BAGIAN 8 — PERTANYAAN KRITIS DAN FUNDAMENTAL

8.1 “Apakah Everything in OOP Harus Menjadi Class?”

Tidak. Ini adalah kesalahan umum “object obsession.”

Fungsi murni (pure functions) yang tidak punya state dan tidak punya side effects tidak perlu dibungkus dalam class hanya karena kita menggunakan bahasa OOP.

# Tidak perlu:

class MathUtils:

def add(self, a, b):

return a + b

# Lebih baik:

def add(a, b):

return a + b

Bahasa seperti Python mendukung fungsi sebagai first-class citizens. Gunakan class ketika ada state yang perlu dikelola, ada behavior yang terkait dengan state tersebut, atau ada kebutuhan untuk membuat banyak instance dengan data berbeda.

8.2 “Apakah OOP Adalah Paradigma Terbaik?”

OOP bukan satu-satunya paradigma, dan bukan selalu yang terbaik:

Functional Programming (FP) (Haskell, Clojure, Erlang) unggul dalam:

  • Concurrency (tidak ada shared mutable state)
  • Transformasi data
  • Code yang lebih mudah di-reason karena tidak ada side effects
  • Mathematical purity

Procedural masih baik untuk:

  • Script sederhana
  • Sistem yang membutuhkan performa maksimal
  • Kode yang bersifat instruksional

Reaktif/Event-driven untuk:

  • UI programming
  • Real-time systems
  • Microservices

Bahasa modern seperti Python, JavaScript, Scala, Kotlin, Rust mendukung multiple paradigma — ini disebut multi-paradigm programming.

8.3 “Apa Itu Coupling dan Cohesion?”

Dua metrik penting untuk mengevaluasi kualitas desain OOP:

Coupling (ketergantungan): Seberapa erat satu module/class bergantung pada module/class lain. Coupling rendah (loose coupling) adalah tujuan yang diinginkan karena memudahkan perubahan dan testing.

Cohesion (kekohesian): Seberapa erat elemen-elemen di dalam satu module/class berkaitan satu sama lain. Cohesion tinggi (high cohesion) adalah yang diinginkan — satu class fokus pada satu hal.

Target: Low coupling, high cohesion.

8.4 “Apa Perbedaan OOP dan Data-Oriented Design?”

Data-Oriented Design (DOD) adalah pendekatan yang sangat berbeda, populer di game development dan sistem performa tinggi. DOD fokus pada bagaimana data disusun di memori untuk efisiensi cache CPU, bukan pada abstraksi objek.

Objek dalam OOP menyimpan data yang beragam bersama-sama (SoA vs AoS), yang bisa tidak efisien untuk cache. DOD memisahkan data berdasarkan tipe dan pola akses.

Ini menunjukkan bahwa OOP, meski sangat umum, bukan jawaban untuk semua masalah.

8.5 “Apa Itu Anemic Domain Model?”

Anemic Domain Model (istilah dari Martin Fowler) adalah ketika class-class domain (Entity) hanya berisi data (getters/setters) tanpa behavior yang berarti, sementara semua logika bisnis ada di “Service” classes. Ini adalah anti-pattern dalam OOP murni karena melanggar enkapsulasi — data dan behavior tidak disatukan.

# Anemic Domain Model (anti-pattern)

class Order:

def __init__(self):

self.items = []

self.status = “pending”

self.total = 0

class OrderService:

def add_item(self, order, item):

order.items.append(item)

order.total += item.price

def confirm_order(self, order):

if len(order.items) == 0:

raise Exception(“Order kosong”)

order.status = “confirmed”

# Rich Domain Model (lebih OOP)

class Order:

def __init__(self):

self._items = []

self._status = “pending”

def add_item(self, item): # Behavior ada di dalam domain

if self._status != “pending”:

raise Exception(“Tidak bisa tambah item ke order yang sudah dikonfirmasi”)

self._items.append(item)

def confirm(self):

if not self._items:

raise Exception(“Order kosong”)

self._status = “confirmed”

@property

def total(self):

return sum(item.price for item in self._items)

BAGIAN 9 — ADVANCED TOPICS

9.1 Metaclass

Metaclass adalah “class dari class” — class yang mendefinisikan bagaimana class itu sendiri dibuat dan berperilaku. Di Python, type adalah metaclass default.

# Class biasa

class MyClass:

pass

# Secara internal, Python melakukan ini:

MyClass = type(‘MyClass’, (object,), {})

# Metaclass kustom

class SingletonMeta(type):

_instances = {}

def __call__(cls, *args, **kwargs):

if cls not in cls._instances:

cls._instances[cls] = super().__call__(*args, **kwargs)

return cls._instances[cls]

class DatabasePool(metaclass=SingletonMeta):

def __init__(self):

self.connections = []

db1 = DatabasePool()

db2 = DatabasePool()

print(db1 is db2) # True — Singleton diimplementasikan via metaclass

Metaclass digunakan oleh framework seperti Django ORM untuk mendefinisikan model database dari class Python biasa.

9.2 Protocol / Structural Subtyping (Python 3.8+)

Protocol memungkinkan “structural subtyping” — class bisa dianggap kompatibel dengan interface jika ia memiliki metode yang diperlukan, tanpa perlu inheritance eksplisit.

from typing import Protocol

class Drawable(Protocol):

def draw(self) -> str: …

class Circle:

def draw(self) -> str:

return “Menggambar lingkaran”

class Square:

def draw(self) -> str:

return “Menggambar kotak”

class Text:

def render(self) -> str: # Tidak punya draw()

return “Render teks”

def render_all(drawables: list[Drawable]) -> None:

for d in drawables:

print(d.draw())

# Circle dan Square kompatibel dengan Drawable meski tidak explicitly inherit

render_all([Circle(), Square()]) # OK

# render_all([Text()]) # Type checker error, tapi runtime tidak

Ini mirip dengan Go interfaces yang implicit.

9.3 Dunder Methods (Magic Methods) di Python

Python menggunakan dunder methods untuk mengintegrasikan objek dengan fitur built-in bahasa. Ini adalah bagian dari Python’s “data model.”

class Temperature:

def __init__(self, celsius):

self._celsius = celsius

def __str__(self): # str(obj) dan print(obj)

return f”{self._celsius}°C”

def __repr__(self): # Representasi debug

return f”Temperature({self._celsius})”

def __add__(self, other): # obj + other

return Temperature(self._celsius + other._celsius)

def __lt__(self, other): # obj < other

return self._celsius < other._celsius

def __eq__(self, other): # obj == other

return self._celsius == other._celsius

def __len__(self): # len(obj) — tidak masuk akal untuk temp, tapi bisa

return int(abs(self._celsius))

def __bool__(self): # bool(obj) — False jika 0°C, True selainnya

return self._celsius != 0

t1 = Temperature(20)

t2 = Temperature(15)

print(t1) # “20°C”

print(t1 + t2) # “35°C”

print(t1 > t2) # True

Referensi lengkap dunder methods: https://docs.python.org/3/reference/datamodel.html

9.4 Dataclass (Python 3.7+)

@dataclass adalah decorator yang secara otomatis menggenerate __init__, __repr__, __eq__, dan lainnya berdasarkan field yang dideklarasikan.

from dataclasses import dataclass, field

from typing import List

@dataclass

class Point:

x: float

y: float

def distance_to(self, other: ‘Point’) -> float:

return ((self.x — other.x)**2 + (self.y — other.y)**2)**0.5

@dataclass

class Student:

name: str

age: int

grades: List[float] = field(default_factory=list)

@property

def average_grade(self):

return sum(self.grades) / len(self.grades) if self.grades else 0

p1 = Point(1, 2)

p2 = Point(4, 6)

print(p1 == Point(1, 2)) # True — __eq__ digenerate otomatis

print(p1) # Point(x=1, y=2) — __repr__ digenerate

print(p1.distance_to(p2)) # 5.0

Dokumentasi: https://docs.python.org/3/library/dataclasses.html

BAGIAN 10 — TESTING DALAM OOP

10.1 Unit Testing dan OOP

OOP yang baik harus mudah di-test. Prinsip SOLID, terutama DIP (Dependency Inversion), sangat membantu karena memungkinkan dependency di-inject sebagai mock/stub.

import unittest

from unittest.mock import MagicMock, patch

class EmailService:

def send_email(self, to, subject, body):

# Implementasi nyata yang mengirim email

pass

class UserRegistrationService:

def __init__(self, email_service: EmailService):

self.email_service = email_service

def register(self, email, password):

if not email or ‘@’ not in email:

raise ValueError(“Email tidak valid”)

if len(password) < 8:

raise ValueError(“Password terlalu pendek”)

# Simpan user…

self.email_service.send_email(

email,

“Selamat datang!”,

“Akun kamu berhasil dibuat.”

)

return {“email”: email, “status”: “registered”}

class TestUserRegistration(unittest.TestCase):

def setUp(self):

self.mock_email_service = MagicMock(spec=EmailService)

self.service = UserRegistrationService(self.mock_email_service)

def test_successful_registration(self):

result = self.service.register(“user@email.com”, “password123”)

self.assertEqual(result[“status”], “registered”)

self.mock_email_service.send_email.assert_called_once()

def test_invalid_email(self):

with self.assertRaises(ValueError):

self.service.register(“invalid-email”, “password123”)

def test_short_password(self):

with self.assertRaises(ValueError):

self.service.register(“user@email.com”, “pass”)

if __name__ == ‘__main__’:

unittest.main()

10.2 Testability sebagai Indikator Desain Baik

Jika kode OOP sulit di-test, itu seringkali tanda desain yang buruk:

  • Terlalu banyak dependensi langsung (violasi DIP)
  • Class terlalu besar (violasi SRP)
  • Terlalu banyak global state
  • Object yang punya side effects yang tidak bisa di-mock

Kode yang mudah di-test biasanya juga mudah dipahami, mudah diubah, dan lebih modular.

BAGIAN 11 — SUMBER DOKUMENTASI RESMI

Dokumentasi Bahasa

Python — Data Model dan OOP: https://docs.python.org/3/tutorial/classes.html https://docs.python.org/3/reference/datamodel.html https://docs.python.org/3/library/abc.html https://docs.python.org/3/library/dataclasses.html

Java — OOP Concepts: https://docs.oracle.com/javase/tutorial/java/concepts/ https://docs.oracle.com/javase/tutorial/java/IandI/index.html https://docs.oracle.com/javase/tutorial/java/OOP/

JavaScript/TypeScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes https://www.typescriptlang.org/docs/handbook/2/classes.html

C++: https://en.cppreference.com/w/cpp/language/classes https://isocpp.org/wiki/faq/classes-and-objects

Buku Referensi Fundamental

“Design Patterns: Elements of Reusable Object-Oriented Software” — Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang of Four, 1994). ISBN: 0–201–63361–2. Buku yang mendefinisikan 23 design patterns klasik.

“Clean Code: A Handbook of Agile Software Craftsmanship” — Robert C. Martin (2008). ISBN: 978–0132350884. Prinsip menulis kode yang bersih termasuk OOP best practices.

“Refactoring: Improving the Design of Existing Code” — Martin Fowler (edisi kedua 2018). ISBN: 978–0134757599. Teknik memperbaiki kode OOP tanpa mengubah behavior.

“Object-Oriented Analysis and Design with Applications” — Grady Booch (1993). Salah satu karya fundamental dalam metodologi OOP.

Paper Akademik Penting

Barbara Liskov, “Data Abstraction and Hierarchy” (1988): Menyatakan Liskov Substitution Principle. Tersedia di ACM Digital Library.

Alan Kay, “The Early History of Smalltalk” (1993): Narasi langsung dari pencipta istilah “object-oriented programming”.

David Parnas, “On the Criteria To Be Used in Decomposing Systems into Modules” (1972): Fondasi information hiding dalam software engineering.

SOLID Principles

Robert C. Martin menjelaskan SOLID secara resmi di: https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf

Dan di blog serta bukunya “Agile Software Development: Principles, Patterns, and Practices” (2002).

RINGKASAN: MENTAL MODEL UNTUK OOP

OOP pada dasarnya adalah tentang modelling dunia sebagai kumpulan entitas yang berinteraksi. Setiap entitas:

  1. Punya identity (satu objek berbeda dari objek lain, bahkan dengan data yang sama)
  2. Punya state (data internal yang bisa berubah)
  3. Punya behavior (metode yang beroperasi pada state)
  4. Berkomunikasi melalui pesan (method calls)

Prinsip-prinsip kunci untuk menggunakan OOP dengan baik:

  • Enkapsulasi: Sembunyikan state internal, ekspos hanya yang diperlukan
  • Abstraksi: Fokus pada apa yang dilakukan, bukan bagaimana
  • Inheritance: Gunakan untuk hubungan “is-a” yang nyata, tapi jangan berlebihan
  • Polimorfisme: Tulis kode yang bekerja dengan abstraksi, bukan implementasi konkret
  • SOLID: Panduan untuk membuat sistem yang bisa berkembang tanpa runtuh
  • Composition over Inheritance: Default ke komposisi, gunakan inheritance hanya saat benar-benar tepat
  • Low coupling, high cohesion: Tujuan akhir desain yang baik
  • Testability: Desain yang baik mudah di-test

OOP adalah alat, bukan agama. Gunakan dengan bijak, kombinasikan dengan paradigma lain saat diperlukan, dan selalu tanyakan: “Apakah ini membuat kode lebih mudah dipahami, diubah, dan di-test?”

Dokumen ini mencakup OOP dari fondasi historis, empat pilar inti, SOLID principles, design patterns, perbedaan antar bahasa, memory management, pertanyaan kritis, advanced topics, hingga sumber referensi resmi. Setiap konsep dijelaskan dengan definisi, motivasi, contoh kode konkret, dan peringatan tentang jebakan umum.

Dadan satria 04/06/2026

--

--

Dadan Satria
Dadan Satria

Written by Dadan Satria

Seolah-olah persiapan reinkarnasi ke dunia medieval