Pertemuan 11 - OOP Python

Pertemuan 11 - OOP Python

Membuat class Produk, Pelanggan, dan Transaksi sebagai perwakilan tabel database dalam kode Python.

Tujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa mampu:

  1. Menjelaskan konsep class dan object serta kaitannya dengan tabel database.
  2. Membuat class Produk beserta atribut dan method-nya.
  3. Membuat class Pelanggan.
  4. Membuat class Transaksi dan ItemTransaksi yang mewakili pola master–detail.
  5. Mengubah baris database menjadi objek Python, dan sebaliknya.

Persiapan

Kebutuhan praktikum:

text
- VS Code
- Project toko-elektronik (dari Pertemuan 09)
- Database database/toko.db beserta isinya (dari Pertemuan 10)
- Virtual environment aktif (venv)

Buka project dan aktifkan venv:

bash
venv\Scripts\activate
macOS / Linux: gunakan source venv/bin/activate.

Pada pertemuan ini kita mengisi folder models/. Buat tiga file kosong berikut di dalamnya:

text
models/
├── produk.py
├── pelanggan.py
└── transaksi.py

Konsep Singkat

Class dan Object

OOP (Object Oriented Programming) adalah cara menulis program dengan membungkus data dan perilaku menjadi satu kesatuan.

  • Class adalah cetakan atau rancangan. Contoh: rancangan "Produk".
  • Object adalah wujud nyata dari cetakan itu. Contoh: Laptop, Mouse, Keyboard — semuanya objek dari class Produk.

Analogi sederhana: class seperti cetakan kue, object adalah kue-kue yang dihasilkan dari cetakan tersebut. Satu cetakan, banyak kue.

Isi sebuah class:

  • Atribut — datanya (nama produk, harga, stok).
  • Method — perilakunya, yaitu fungsi milik class (menghitung nilai persediaan, mengurangi stok).

Hubungan dengan Database (Pertemuan 10)

Inilah kunci pertemuan ini. Class bukan hal baru yang terpisah — ia adalah perwakilan tabel di dalam kode Python:

Pemetaan tabel ke class
Database Python
Tabel produk Class Produk
Satu baris di tabel Satu objek
Kolom (nama_produk, harga) Atribut (self.nama_produk, self.harga)

Perhatikan juga: pola master–detail di database (penjualan + detail_penjualan) diterjemahkan menjadi satu objek Transaksi yang memuat daftar objek ItemTransaksi.

Mengapa Repot-repot Memakai Class?

Tanpa class, data produk berserakan dalam variabel terpisah. Dengan class, data dan perilakunya menyatu:

python
# tanpa class - data terpisah, perhitungan ditulis berulang
nama = "Laptop"
harga = 8500000
stok = 10
nilai = harga * stok

# dengan class - menyatu dan bisa dipakai ulang
laptop = Produk("Laptop", "Elektronik", 8500000, 10)
nilai = laptop.nilai_persediaan()

Langkah 1 - Class Produk

Buka models/produk.py, lalu tulis:

python
class Produk:
    """Mewakili satu baris pada tabel produk."""

    def __init__(self, nama_produk, kategori, harga, stok=0, id_produk=None):
        self.id_produk = id_produk
        self.nama_produk = nama_produk
        self.kategori = kategori
        self.harga = harga
        self.stok = stok

    def nilai_persediaan(self):
        return self.harga * self.stok

    def __str__(self):
        return f"{self.nama_produk} ({self.kategori}) - Rp {self.harga:,} | stok: {self.stok}"

Penjelasan bagian-bagiannya:

Bagian Arti
class Produk: Mendefinisikan class (nama pakai PascalCase, ingat PEP8 di Pertemuan 09)
__init__ Constructor — dijalankan otomatis saat objek dibuat
self Menunjuk pada objek itu sendiri; selalu menjadi parameter pertama
self.harga = harga Menyimpan nilai ke dalam atribut objek
stok=0 Nilai bawaan bila tidak diisi
id_produk=None Kosong dulu; diisi setelah data disimpan ke database
__str__ Menentukan tampilan objek saat di-print()

Uji di main.py:

python
from models.produk import Produk

laptop = Produk("Laptop", "Elektronik", 8500000, 10)
mouse = Produk("Mouse", "Aksesoris", 150000, 50)

print(laptop)
print("Nilai persediaan:", f"Rp {laptop.nilai_persediaan():,}")

Output:

text
Laptop (Elektronik) - Rp 8,500,000 | stok: 10
Nilai persediaan: Rp 85,000,000
Satu class (Produk), dua objek (laptop, mouse). Masing-masing punya data sendiri, tetapi berbagi perilaku yang sama.

Langkah 2 - Menambah Method dan Validasi

Method membuat objek bisa "melakukan sesuatu". Tambahkan method pengelolaan stok ke dalam class Produk:

python
    def kurangi_stok(self, jumlah):
        if jumlah <= 0:
            raise ValueError("Jumlah harus lebih dari 0")
        if jumlah > self.stok:
            raise ValueError(f"Stok {self.nama_produk} tidak cukup (tersisa {self.stok})")
        self.stok -= jumlah
        return self.stok

    def tambah_stok(self, jumlah):
        self.stok += jumlah
        return self.stok

Uji:

python
laptop = Produk("Laptop", "Elektronik", 8500000, 10)

laptop.kurangi_stok(3)
print("Stok sekarang:", laptop.stok)

try:
    laptop.kurangi_stok(999)          # sengaja melebihi stok
except ValueError as e:
    print("Error:", e)

Output:

text
Stok sekarang: 7
Error: Stok Laptop tidak cukup (tersisa 7)
Inilah keunggulan OOP: aturan "stok tidak boleh minus" melekat pada class Produk. Di mana pun produk dipakai, aturan itu otomatis berlaku — tidak perlu ditulis ulang.

raise ValueError(...) memunculkan error secara sengaja; try ... except menangkapnya agar program tidak berhenti mendadak.

Langkah 3 - Class Pelanggan

Polanya sama, lebih sederhana. Buka models/pelanggan.py:

python
class Pelanggan:
    """Mewakili satu baris pada tabel pelanggan."""

    def __init__(self, nama, telepon="", alamat="", id_pelanggan=None):
        self.id_pelanggan = id_pelanggan
        self.nama = nama
        self.telepon = telepon
        self.alamat = alamat

    def __str__(self):
        return f"{self.nama} - {self.telepon} ({self.alamat})"

Uji:

python
from models.pelanggan import Pelanggan

budi = Pelanggan("Budi Santoso", "08123456789", "Jakarta")
print(budi)

Output:

text
Budi Santoso - 08123456789 (Jakarta)

Langkah 4 - Class Transaksi (Master-Detail)

Bagian terpenting. Ingat rancangan database: satu nota (penjualan) memuat banyak barang (detail_penjualan). Dalam OOP, ini menjadi satu objek Transaksi yang menyimpan daftar objek ItemTransaksi.

Buka models/transaksi.py:

python
class ItemTransaksi:
    """Satu baris barang di dalam nota (mewakili detail_penjualan)."""

    def __init__(self, produk, qty):
        self.produk = produk
        self.qty = qty

    def subtotal(self):
        return self.produk.harga * self.qty

    def __str__(self):
        return f"{self.produk.nama_produk} x{self.qty} = Rp {self.subtotal():,}"


class Transaksi:
    """Mewakili satu nota (tabel penjualan) beserta isinya."""

    def __init__(self, tanggal, pelanggan, id_penjualan=None):
        self.id_penjualan = id_penjualan
        self.tanggal = tanggal
        self.pelanggan = pelanggan
        self.items = []                      # daftar ItemTransaksi

    def tambah_item(self, produk, qty):
        produk.kurangi_stok(qty)             # stok otomatis berkurang
        self.items.append(ItemTransaksi(produk, qty))

    def total(self):
        return sum(item.subtotal() for item in self.items)

    def jumlah_barang(self):
        return sum(item.qty for item in self.items)

    def cetak_nota(self):
        print("=" * 42)
        print(f"NOTA PENJUALAN   {self.tanggal}")
        print(f"Pelanggan: {self.pelanggan.nama}")
        print("-" * 42)
        for item in self.items:
            print(" ", item)
        print("-" * 42)
        print(f"TOTAL: Rp {self.total():,}")
        print("=" * 42)

Perhatikan tiga hal penting:

  • self.items = [] — sebuah objek dapat menyimpan daftar objek lain. Inilah wujud relasi master–detail dalam OOP.
  • tambah_item() memanggil produk.kurangi_stok(qty)satu objek memakai method objek lain, sehingga stok terjaga otomatis.
  • total() menjumlahkan seluruh subtotal — tidak perlu dihitung manual dari luar.

Uji di main.py:

python
from models.produk import Produk
from models.pelanggan import Pelanggan
from models.transaksi import Transaksi

laptop = Produk("Laptop", "Elektronik", 8500000, 10)
mouse = Produk("Mouse", "Aksesoris", 150000, 50)
budi = Pelanggan("Budi Santoso", "08123456789", "Jakarta")

trx = Transaksi("2025-03-01", budi)
trx.tambah_item(laptop, 1)
trx.tambah_item(mouse, 2)

trx.cetak_nota()
print("\nStok Laptop sekarang:", laptop.stok)
print("Stok Mouse sekarang :", mouse.stok)

Output:

text
==========================================
NOTA PENJUALAN   2025-03-01
Pelanggan: Budi Santoso
------------------------------------------
  Laptop x1 = Rp 8,500,000
  Mouse x2 = Rp 300,000
------------------------------------------
TOTAL: Rp 8,800,000
==========================================

Stok Laptop sekarang: 9
Stok Mouse sekarang : 48
Bandingkan dengan Pertemuan 10: totalnya sama persis (Rp 8.800.000) dan stok Laptop juga menjadi 9. Bedanya, di sini semuanya diurus oleh objek — kode di main.py menjadi jauh lebih pendek dan mudah dibaca.

Langkah 5 - Menghubungkan Class dengan Database

Sekarang jembatannya: mengubah baris database menjadi objek, dan objek menjadi baris database. Pola inilah yang dipakai penuh pada CRUD (Pertemuan 12).

Buat file uji_db.py:

python
import sqlite3
from models.produk import Produk

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

# --- DARI DATABASE MENJADI OBJEK ---
cursor.execute("SELECT id_produk, nama_produk, kategori, harga, stok FROM produk")
baris_data = cursor.fetchall()

daftar_produk = []
for baris in baris_data:
    p = Produk(
        id_produk=baris[0],
        nama_produk=baris[1],
        kategori=baris[2],
        harga=baris[3],
        stok=baris[4],
    )
    daftar_produk.append(p)

print("Objek hasil dari database:")
for p in daftar_produk:
    print(" -", p)

# setelah menjadi objek, method-nya bisa langsung dipakai
total_aset = sum(p.nilai_persediaan() for p in daftar_produk)
print("\nTotal nilai persediaan:", f"Rp {total_aset:,}")

# --- DARI OBJEK MENJADI BARIS DATABASE ---
speaker = Produk("Speaker", "Elektronik", 750000, 20)
cursor.execute(
    "INSERT INTO produk (nama_produk, kategori, harga, stok) VALUES (?, ?, ?, ?)",
    (speaker.nama_produk, speaker.kategori, speaker.harga, speaker.stok),
)
speaker.id_produk = cursor.lastrowid       # objek kini punya id dari database
conn.commit()

print(f"\nProduk baru tersimpan dengan id: {speaker.id_produk}")
conn.close()

Output:

text
Objek hasil dari database:
 - Laptop (Elektronik) - Rp 8,500,000 | stok: 10
 - Monitor (Elektronik) - Rp 2,000,000 | stok: 15
 - Mouse (Aksesoris) - Rp 150,000 | stok: 50

Total nilai persediaan: Rp 122,500,000

Produk baru tersimpan dengan id: 4
Inilah inti pertemuan ini. Database menyimpan data sebagai baris; Python mengolahnya sebagai objek. Menerjemahkan keduanya bolak-balik adalah pekerjaan sehari-hari pengembang aplikasi — dan itulah yang akan kita rapikan menjadi CRUD pada Pertemuan 12.

Referensi Fungsi Penting

Anatomi Class

Elemen Penulisan Fungsi
Definisi class class Produk: Membuat cetakan (PascalCase)
Constructor def __init__(self, ...): Dijalankan saat objek dibuat
Atribut self.harga = harga Menyimpan data objek
Method def nilai_persediaan(self): Perilaku objek
Tampilan def __str__(self): Hasil saat objek di-print()
Membuat objek p = Produk("Laptop", ...) Mewujudkan class
Memanggil method p.nilai_persediaan() Menjalankan perilaku
Mengakses atribut p.harga Membaca data objek

Penanganan Error

Perintah Fungsi
raise ValueError("pesan") Memunculkan error secara sengaja
try: ... except ValueError as e: Menangkap error agar program tetap berjalan

Pola Baris Database → Objek

python
cursor.execute("SELECT id_produk, nama_produk, kategori, harga, stok FROM produk")
daftar = [
    Produk(id_produk=b[0], nama_produk=b[1], kategori=b[2], harga=b[3], stok=b[4])
    for b in cursor.fetchall()
]

Ingin Mendalami OOP?

Pertemuan ini membahas fondasi OOP secukupnya untuk membangun aplikasi penjualan: class, object, atribut, dan method. Sesungguhnya OOP memiliki empat pilar yang lebih luas:

Pilar Inti
Encapsulation Melindungi data agar tidak diubah sembarangan
Inheritance Membuat class baru dengan mewarisi class lain
Polymorphism Satu perintah, perilaku berbeda pada tiap objek
Abstraction Kontrak yang wajib dipenuhi class turunan

Keempatnya tidak diperlukan untuk menyelesaikan aplikasi kita (Pertemuan 12–16), sehingga tidak dibahas di sini agar praktikum tetap fokus.

Bagi yang ingin memahami OOP secara utuh, tersedia materi tambahan Bonus Materi - OOP Lanjutan (opsional, bernilai tambahan).

Pengujian

Pastikan hasil praktikum sesuai berikut:

  1. File models/produk.py, models/pelanggan.py, dan models/transaksi.py terisi.
  2. print(laptop) menampilkan Laptop (Elektronik) - Rp 8,500,000 | stok: 10.
  3. laptop.nilai_persediaan() menghasilkan 85.000.000.
  4. kurangi_stok() dengan jumlah melebihi stok memunculkan pesan error, bukan stok minus.
  5. trx.cetak_nota() menampilkan TOTAL: Rp 8.800.000 (Laptop 1 + Mouse 2).
  6. Stok berkurang otomatis setelah transaksi: Laptop 10 → 9, Mouse 50 → 48.

Verifikasi cepat:

python
laptop = Produk("Laptop", "Elektronik", 8500000, 10)
mouse = Produk("Mouse", "Aksesoris", 150000, 50)
budi = Pelanggan("Budi Santoso")

trx = Transaksi("2025-03-01", budi)
trx.tambah_item(laptop, 1)
trx.tambah_item(mouse, 2)

assert trx.total() == 8800000
assert trx.jumlah_barang() == 3
assert laptop.stok == 9 and mouse.stok == 48
print("Semua pengujian lolos.")

Troubleshooting

Masalah Penyebab Solusi
ModuleNotFoundError: No module named 'models' Program dijalankan dari folder yang salah Jalankan python main.py dari folder utama project
TypeError: __init__() missing argument Ada parameter wajib yang tidak diisi Lengkapi argumen saat membuat objek
AttributeError: object has no attribute Nama atribut salah ketik Cocokkan dengan yang ditulis di __init__
Method dipanggil tanpa hasil Lupa tanda kurung Tulis p.nilai_persediaan(), bukan p.nilai_persediaan
print(objek) menampilkan <object at 0x...> Class belum punya __str__ Tambahkan method __str__
Lupa menulis self Parameter pertama method wajib self def nilai_persediaan(self):

Tugas Praktikum

Kembangkan class yang sudah dibuat:

  1. Tambahkan method diskon(self, persen) pada class Produk yang mengembalikan harga setelah diskon (tanpa mengubah harga aslinya).
  2. Tambahkan method is_stok_menipis(self) pada class Produk yang mengembalikan True bila stok kurang dari 5.
  3. Tambahkan method jumlah_item(self) pada class Transaksi yang mengembalikan banyaknya jenis barang dalam nota (berbeda dengan jumlah_barang() yang menghitung total unit).
  4. Buat satu transaksi baru berisi 3 jenis produk berbeda, cetak notanya, lalu tampilkan stok akhir tiap produk.
  5. Buat program yang membaca seluruh produk dari database, mengubahnya menjadi objek, lalu menampilkan produk yang stoknya menipis (memakai method dari nomor 2).

Kumpulkan berupa file .py beserta tangkapan layar hasil menjalankannya (format PDF).

Kesimpulan

Pada pertemuan ini mahasiswa mempelajari OOP bukan sebagai teori terpisah, melainkan sebagai cara Python mewakili tabel database: satu tabel menjadi satu class, satu baris menjadi satu objek, dan kolom menjadi atribut. Class Produk dibekali method yang menjaga aturan (stok tidak boleh minus), sementara class Transaksi dan ItemTransaksi menerjemahkan pola master–detail menjadi objek yang memuat daftar objek lain. Mahasiswa juga berlatih mengubah baris database menjadi objek dan sebaliknya. Pada pertemuan berikutnya, pola tersebut dirapikan menjadi operasi CRUD (Create, Read, Update, Delete) yang lengkap untuk data produk.