Ketika pipeline hanya punya dua langkah, fungsi lepas masih mudah diikuti. Namun pipeline produksi punya belasan langkah dengan aturan yang berbeda-beda, dan tanpa wadah yang rapi, kode berubah menjadi tumpukan fungsi yang saling memanggil tanpa bentuk yang jelas. Pemrograman berorientasi objek (OOP) menggabungkan data dan perilaku di dalam satu class: dataclass menampung record, class menampung langkah, dan komposisi menyusun pipeline dari daftar langkah. Chapter ini membangun ketiganya di atas contoh penjualan yang sama, dari dataclass Transaksi hingga class Pipeline yang menggabungkan banyak step.

Record Data dengan dataclass #

Dict cocok untuk record mentah, tetapi dict tidak menjaga bentuk: kolom bisa hilang, bertambah, atau salah nama tanpa ada yang protes. Dataclass memperbaiki hal tersebut dengan mendeklarasikan field secara eksplisit:

python
from dataclasses import dataclass


@dataclass
class Transaksi:
    """Record satu baris transaksi penjualan."""

    id: str
    kasir: str
    produk: str
    qty: int
    harga: int

    @property
    def total(self):
        return self.qty * self.harga


t1 = Transaksi("TRX-001", "Budi", "kopi lampung", 2, 24000)
print(t1)
print(t1.produk.title())
print("total Rp" + str(t1.total))

Output yang Dihasilkan:

text
Transaksi(id='TRX-001', kasir='Budi', produk='kopi lampung', qty=2, harga=24000)
Kopi Lampung
total Rp48000

Penulisan id: str adalah anotasi tipe — bagian dari sintaks deklarasi field dataclass; topik anotasi dibahas tuntas di chapter Type Hints, dan untuk sekarang cukup dibaca sebagai “field id bertipe teks”. Decorator @dataclass otomatis menghasilkan __init__ dari daftar field, __repr__ yang tampak pada baris pertama output, dan __eq__ yang membandingkan dua record berdasarkan nilai. Property total menghitung atribut turunan tanpa disimpan — t1.total terbaca seperti field biasa. Dokumentasi lengkapnya ada di https://docs.python.org/3/library/dataclasses.html .

Class PipelineStep dan Komposisi Pipeline #

Satu langkah pipeline punya nama dan satu tugas. Class dasar PipelineStep mendefinisikan kontrak tersebut; langkah konkrit tinggal mengisi method proses(). Class Pipeline lalu mengomposisikan daftar langkah — inilah komposisi: pipeline memiliki sekumpulan step, bukan mewarisi dari step. Dokumentasi class dan inheritance ada di https://docs.python.org/3/tutorial/classes.html .

python
from dataclasses import dataclass


@dataclass
class Transaksi:
    """Record satu baris transaksi penjualan."""

    id: str
    kasir: str
    produk: str
    qty: int
    harga: int

    @property
    def total(self):
        return self.qty * self.harga


class PipelineStep:
    """Kelas dasar untuk satu langkah pipeline."""

    def __init__(self, nama):
        self.nama = nama

    def proses(self, data):
        raise NotImplementedError("setiap langkah harus mendefinisikan proses()")

    def __repr__(self):
        return f"<{self.__class__.__name__} nama={self.nama!r}>"


class ValidasiHarga(PipelineStep):
    """Langkah konkrit: buang record dengan harga tidak positif."""

    def proses(self, data):
        hasil = []
        for t in data:
            if t.harga <= 0:
                print(f"  {t.id} ditolak: harga {t.harga}")
            else:
                hasil.append(t)
        return hasil


class NormalisasiProduk(PipelineStep):
    """Langkah konkrit: rapikan nama produk di tempat."""

    def proses(self, data):
        for t in data:
            t.produk = t.produk.title()
        return data


class Pipeline:
    """Komposisi: menampung daftar langkah lalu menjalankannya berurutan."""

    def __init__(self, nama, langkah):
        self.nama = nama
        self.langkah = langkah

    def jalankan(self, data):
        print(f"Menjalankan pipeline {self.nama!r}")
        for langkah in self.langkah:
            data = langkah.proses(data)
            print(f"  selesai: {langkah!r}")
        return data


mentah = [
    Transaksi("TRX-001", "Budi", "kopi lampung", 2, 24000),
    Transaksi("TRX-002", "Siti", "teh melati", 3, 15000),
    Transaksi("TRX-003", "Dewi", "gula aren", 1, -5000),
]

pipeline = Pipeline(
    "penjualan-harian",
    [ValidasiHarga("validasi"), NormalisasiProduk("normalisasi")],
)
bersih = pipeline.jalankan(mentah)

print("Hasil:")
for t in bersih:
    print(" ", t)
print("Total pendapatan:", sum(t.total for t in bersih))
bash
python3 pipeline_oop.py

Output yang Dihasilkan:

text
Menjalankan pipeline 'penjualan-harian'
  TRX-003 ditolak: harga -5000
  selesai: <ValidasiHarga nama='validasi'>
  selesai: <NormalisasiProduk nama='normalisasi'>
Hasil:
  Transaksi(id='TRX-001', kasir='Budi', produk='Kopi Lampung', qty=2, harga=24000)
  Transaksi(id='TRX-002', kasir='Siti', produk='Teh Melati', qty=3, harga=15000)
Total pendapatan: 93000

Mari kita telaah baris-per-baris kode di atas:

  1. Baris 22-23 (__init__ pada PipelineStep): constructor menyimpan nama langkah ke self.nama; setiap instance membawa konfigurasinya sendiri, jadi dua instance bisa bernama berbeda untuk tugas yang sama.
  2. Baris 25-26 (proses raise NotImplementedError): class dasar menyatakan kontrak — anak yang lupa mendefinisikan proses() langsung terdengar sebagai galat saat dipanggil, bukan senyap mengembalikan None.
  3. Baris 28-29 (__repr__): representasi ringkas muncul pada baris selesai: <ValidasiHarga nama='validasi'> — log yang enak dibaca tanpa kerja tambahan.
  4. Baris 32-42 (ValidasiHarga): class tersebut mewarisi PipelineStep lalu mengisi proses(); record dengan harga negatif ditolak lebih dulu, terlihat pada baris kedua output.
  5. Baris 54-66 (Pipeline.jalankan): komposisi bekerja di sini — self.langkah adalah list berisi instance PipelineStep; setiap langkah menerima data, mengembalikan data, lalu hasilnya menjadi masukan langkah berikutnya. Menambah langkah baru cukup menambah satu elemen list, tanpa menyentuh class Pipeline.

Kapan Memakai Class, Kapan Cukup Fungsi #

OOP bukan tujuan, melainkan alat. Panduan berikut menjaga pilihan tetap sederhana:

SituasiBentuk yang cocok
Record dengan beberapa kolom dan sedikit perilakudataclass
Langkah dengan konfigurasi atau keadaan sendiriclass turunan PipelineStep
Transformasi satu masukan menjadi satu keluaranfungsi murni
Skrip sekali jalan di bawah 50 barisfungsi + modul, class justru berlebihan

Class Pipeline menampung langkah; setiap langkah menerima dan mengembalikan data. Kontrak seperti itu paling enak dibaca bila jenis data dituliskan secara eksplisit pada fungsi — dan penulisan eksplisit tersebut adalah topik chapter berikutnya, Type Hints.