Sepuluh chapter sebelumnya memberi kita bahan bangun — classdef, properties, methods, constructor, access control, dua semantik, inheritance, polymorphism, events — tetapi tidak satu pun memberi tahu kapan memakai yang mana. Desain class yang buruk terasa sehat di hari pertama: semua data ditumpuk jadi satu class raksasa, semua method dibuat publik, validasi ditempel di sana-sini. Enam bulan kemudian class itu menjadi bagian kode yang semua orang takut sentuh. Chapter penutup ini merangkum proses dan prinsip merancang class yang sehat, lalu menerapkannya bertahap pada studi kasus KeranjangBelanja — dari daftar kata benda hingga class teruji yang invariantnya tidak bisa dilanggar dari luar. Cakupannya:
- proses desain empat langkah: entitas → state → perilaku → relasi
- prinsip: satu tanggung jawab, komposisi ketimbang inheritance, API minimal
- validasi di gerbang dan invariant
- kapan me-refactor
structmenjadi class
Proses Desain: Empat Pertanyaan Berurutan #
Merancang class dimulai bukan dari classdef, melainkan dari keempat pertanyaan berikut:
Diagram tersebut dibaca sebagai urutan keputusan, bukan tahap administratif. Langkah pertama memilah kata benda domain — dari kasus toko kelontong kita mendapat Keranjang, Item, Diskon; tidak semuanya layak jadi class (langkah keempat boleh membuang kandidat). Langkah kedua menanya state yang benar-benar perlu diingat — keranjang perlu daftar item, bukan total (total selalu bisa dihitung ulang dari daftar). Langkah ketiga menyetel perilaku atas state itu. Langkah keempat memutuskan keluarga dan relasi: KeranjangBelanja kita pilih sebagai handle class karena satu keranjang dipegang banyak fungsi (tambah item, cetak, tagih) yang harus melihat state yang sama.
Prinsip yang Menjaga Class Tetap Sehat #
| Prinsip | Artinya di praktik | Tanda pelanggaran |
|---|---|---|
| Single responsibility | satu class punya satu alasan untuk berubah | method ikut-ikutkan hitungan, tampilan, dan I/O file sekaligus |
| Favor komposisi | property berisi object class lain; bukan rantai < yang panjang | hierarki empat level hanya demi berbagi satu method |
| Hindari god class | class bisa dijelaskan satu kalimat | belasan property, ratusan baris, semua orang menaruh data di sini |
| Validasi di gerbang | cek di constructor/method penulis sebelum state terbentuk | validasi tercecer di setiap pemanggil |
| API publik minimal | helper dibuat private, hanya operasi bermakna yang publik | semua method publik “biar aman” |
| Dokumentasi help | blok komentar %NAMACLASS di atas berkas | help NamaClass tampil kosong |
Dua prinsip pertama layak diperdekat. Single responsibility untuk kasus kita: class keranjang hanya menjaga daftar item; mencetak struk ke PDF atau menghitung diskon member adalah tanggung jawab class lain. Komposisi tampil begitu tanggung jawab lain itu datang: property diskon yang berisi object KebijakanDiskon — keranjang memiliki kebijakan, bukan merupakan kebijakan — sehingga mengganti aturan diskon tidak menyentuh satu baris kode keranjang. Inheritance (chapter sebelumnya) kita pakai hanya saat relasi is-a tulus: Persegi adalah Bentuk; KeranjangBelanja bukan Item.
Studi Kasus: Merancang KeranjangBelanja #
Empat pertanyaan tadi menghasilkan rancangan berikut: state satu property items (struct array nama/harga/jumlah, privat), perilaku publik tambah/hapus/total/jumlahItem/cetak, helper cari dan periksaInput di blok private, dengan tiga invariant: harga positif, jumlah bulat positif, dan nama unik. Berkas KeranjangBelanja.m:
classdef KeranjangBelanja < handle
%KERANJANGBELANJA Studi kasus: keranjang belanja dengan invariant.
% Invariant yang dijaga class ini:
% - harga item selalu positif
% - jumlah item selalu bilangan bulat positif
% - nama item unik; item yang sama menambah jumlah, bukan baris baru
% Items hanya bisa diubah lewat method tambah()/hapus().
properties (SetAccess = private)
items % struct array berisi field nama, harga, jumlah
end
methods
function obj = KeranjangBelanja()
% Constructor: keranjang dimulai kosong.
obj.items = struct('nama', {}, 'harga', {}, 'jumlah', {});
end
function tambah(obj, nama, harga, jumlah)
% tambah Menambah item baru atau menambah jumlah item lama.
obj.periksaInput(nama, harga, jumlah);
idx = obj.cari(nama);
if isempty(idx)
posisi = numel(obj.items) + 1;
obj.items(posisi).nama = nama;
obj.items(posisi).harga = harga;
obj.items(posisi).jumlah = jumlah;
else
obj.items(idx).jumlah = obj.items(idx).jumlah + jumlah;
end
end
function hapus(obj, nama)
% hapus Membuang satu baris item bernama nama.
idx = obj.cari(nama);
if isempty(idx)
error('KeranjangBelanja:item', ...
'item %s tidak ada di keranjang', nama);
end
obj.items(idx) = [];
end
function t = total(obj)
% total Total harga seluruh isi keranjang.
t = 0;
for i = 1:numel(obj.items)
t = t + obj.items(i).harga * obj.items(i).jumlah;
end
end
function n = jumlahItem(obj)
% jumlahItem Banyak baris item berbeda.
n = numel(obj.items);
end
function cetak(obj)
% cetak Laporan isi keranjang dalam format tabel sederhana.
if obj.jumlahItem() == 0
fprintf('Keranjang kosong\n');
return
end
for i = 1:numel(obj.items)
fprintf('%-6s x%-2d @ Rp%-6d = Rp%d\n', ...
obj.items(i).nama, obj.items(i).jumlah, ...
obj.items(i).harga, ...
obj.items(i).harga * obj.items(i).jumlah);
end
fprintf('TOTAL Rp%d\n', obj.total());
end
end
methods (Access = private)
function idx = cari(obj, nama)
% cari Indeks item bernama nama ([] bila tidak ditemukan).
idx = find(strcmp({obj.items.nama}, nama), 1);
end
function periksaInput(obj, nama, harga, jumlah)
% Validasi di gerbang: seluruh aturan invariant diperiksa
% satu kali di sini, dipakai oleh semua method yang menulis.
if ~ischar(nama) || isempty(nama)
error('KeranjangBelanja:input', ...
'nama harus berupa teks tak kosong');
end
if ~(isscalar(harga) && harga > 0)
error('KeranjangBelanja:input', 'harga harus positif');
end
if ~(isscalar(jumlah) && jumlah > 0 && jumlah == floor(jumlah))
error('KeranjangBelanja:input', ...
'jumlah harus bilangan bulat positif');
end
end
end
endMari kita bedah baris-baris kode di atas:
- Baris 1 (
classdef KeranjangBelanja < handle): keputusan relasi — satu keranjang yang sama dipakai lintas fungsi, jadi handle class; method penulis tidak perlu mengembalikanobj. - Baris 9-11 (
properties (SetAccess = private)): satu-satunya state; kode luar bisa membacak.items(misalnya untuk laporan) tetapi tidak bisa menulisnya — seluruh perubahan dipaksa lewat method. - Baris 19-31 (
tambah): gerbang validasi (periksaInput) lalu logika unik-nama: nama lama menambahjumlah, nama baru menambah baris — invariant ketiga dijaga di satu tempat. - Baris 43-49 (
total): bukan property, melainkan method — nilai turunan dihitung saat dibutuhkan sehingga tidak pernah basi. - Baris 72-92 (
methods (Access = private)):caridanperiksaInputadalah detail mesin; API publik tetap lima method, dan komentar help pada setiap method menjadi dokumentasihelp KeranjangBelanja.
Uji Invariant dari Luar #
Skrip uji_keranjang.m memainkan dua peran: pengguna normal dan penyerang. Pengguna normal mengisi keranjang:
k = KeranjangBelanja();
k.tambah('Beras', 12000, 2);
k.tambah('Gula', 15000, 1);
k.tambah('Beras', 12000, 1); % nama sama: jumlah bertambah, bukan baris baru
k.cetak();
fprintf('jumlahItem : %d baris\n', k.jumlahItem());Output yang Dihasilkan:
Beras x3 @ Rp12000 = Rp36000
Gula x1 @ Rp15000 = Rp15000
TOTAL Rp51000
jumlahItem : 2 barisTiga pemanggilan tambah hanya menghasilkan dua baris — ‘Beras’ tergabung menjadi x3. Penyerang lalu mencoba melanggar ketiga invariant dan menulis state internal:
try
k.tambah('Teh', -5000, 1);
catch err
fprintf('harga negatif : ditolak (%s)\n', err.message);
end
try
k.tambah('Teh', 5000, 1.5);
catch err
fprintf('jumlah pecahan : ditolak (%s)\n', err.message);
end
try
k.hapus('Kopi');
catch err
fprintf('item tak ada : ditolak (%s)\n', err.message);
end
try
k.items(1).jumlah = 99;
catch
fprintf('set k.items : ditolak (SetAccess = private)\n');
endOutput yang Dihasilkan:
harga negatif : ditolak (harga harus positif)
jumlah pecahan : ditolak (jumlah harus bilangan bulat positif)
item tak ada : ditolak (item Kopi tidak ada di keranjang)
set k.items : ditolak (SetAccess = private)Keputusan handle class terbukti terakhir: dua nama untuk satu keranjang, dan perubahan lewat salah satunya terlihat keduanya (bagian akhir skrip uji):
k2 = k; % k2 menunjuk keranjang yang sama dengan k
k2.tambah('Teh', 5000, 2); % menambah lewat k2 ...
fprintf('jumlahItem via k : %d baris (terlihat oleh k juga)\n', k.jumlahItem());
fprintf('total via k : Rp%d\n', k.total());Output yang Dihasilkan:
jumlahItem via k : 2 baris (terlihat oleh k juga)
total via k : Rp46000Refactoring struct menjadi Class #
Chapter Konsep OOP membuka dengan bahaya struct prosedural; penutupnya adalah daftar kapan migrasi itu benar-benar berbayar:
| Tanda pada kode struct | Perlukah class? |
|---|---|
| Banyak fungsi menulis field yang sama dengan aturan yang sama | ya — aturan dikunci di satu class |
| Field butuh default, validasi, atau format khusus | ya — constructor + validasi gerbang |
| Data sekali pakai, dibuat lalu dibuang dalam satu fungsi | tidak — struct tetap tepat |
| Nilai murni hasil hitung (matriks, vektor, hasil sensor) | tidak — value class tipis atau struct cukup |
| Objek punya identitas dan siklus hidup yang panjang | ya — pertimbangkan handle class |
Ringkasan #
Merancang class berjalan empat langkah — entitas, state, perilaku, relasi — lalu dijaga prinsip: satu tanggung jawab per class, komposisi ketimbang rantai inheritance, API publik seminimal yang bermakna, validasi dikumpulkan di gerbang penulisan state, dan dokumentasi help di setiap berkas. Studi kasus KeranjangBelanja menunjukkan hasil akhirnya: satu property privat, lima method publik, dua helper private, tiga invariant yang semuanya punya baris pembuktian di uji_keranjang.m — dan pilihan handle class yang membuat satu keranjang bisa dipakai lintas fungsi tanpa disalin. Dengan ini seluruh perkakas OOP MATLAB kita miliki; sisanya adalah latihan membaca domain dan berani memecah class yang mulai membesar. Berkas KeranjangBelanja.m dan uji_keranjang.m di bagian bawah halaman memuat studi kasus lengkap yang sudah teruji.