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 struct menjadi 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 #

PrinsipArtinya di praktikTanda pelanggaran
Single responsibilitysatu class punya satu alasan untuk berubahmethod ikut-ikutkan hitungan, tampilan, dan I/O file sekaligus
Favor komposisiproperty berisi object class lain; bukan rantai < yang panjanghierarki empat level hanya demi berbagi satu method
Hindari god classclass bisa dijelaskan satu kalimatbelasan property, ratusan baris, semua orang menaruh data di sini
Validasi di gerbangcek di constructor/method penulis sebelum state terbentukvalidasi tercecer di setiap pemanggil
API publik minimalhelper dibuat private, hanya operasi bermakna yang publiksemua method publik “biar aman”
Dokumentasi helpblok komentar %NAMACLASS di atas berkashelp 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:

matlab
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
end

Mari kita bedah baris-baris kode di atas:

  1. Baris 1 (classdef KeranjangBelanja < handle): keputusan relasi — satu keranjang yang sama dipakai lintas fungsi, jadi handle class; method penulis tidak perlu mengembalikan obj.
  2. Baris 9-11 (properties (SetAccess = private)): satu-satunya state; kode luar bisa membaca k.items (misalnya untuk laporan) tetapi tidak bisa menulisnya — seluruh perubahan dipaksa lewat method.
  3. Baris 19-31 (tambah): gerbang validasi (periksaInput) lalu logika unik-nama: nama lama menambah jumlah, nama baru menambah baris — invariant ketiga dijaga di satu tempat.
  4. Baris 43-49 (total): bukan property, melainkan method — nilai turunan dihitung saat dibutuhkan sehingga tidak pernah basi.
  5. Baris 72-92 (methods (Access = private)): cari dan periksaInput adalah detail mesin; API publik tetap lima method, dan komentar help pada setiap method menjadi dokumentasi help KeranjangBelanja.

Uji Invariant dari Luar #

Skrip uji_keranjang.m memainkan dua peran: pengguna normal dan penyerang. Pengguna normal mengisi keranjang:

matlab
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:

text
Beras  x3  @ Rp12000  = Rp36000
Gula   x1  @ Rp15000  = Rp15000
TOTAL                   Rp51000
jumlahItem : 2 baris

Tiga pemanggilan tambah hanya menghasilkan dua baris — ‘Beras’ tergabung menjadi x3. Penyerang lalu mencoba melanggar ketiga invariant dan menulis state internal:

matlab
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');
end

Output yang Dihasilkan:

text
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):

matlab
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:

text
jumlahItem via k  : 2 baris (terlihat oleh k juga)
total via k       : Rp46000

Refactoring struct menjadi Class #

Chapter Konsep OOP membuka dengan bahaya struct prosedural; penutupnya adalah daftar kapan migrasi itu benar-benar berbayar:

Tanda pada kode structPerlukah class?
Banyak fungsi menulis field yang sama dengan aturan yang samaya — aturan dikunci di satu class
Field butuh default, validasi, atau format khususya — constructor + validasi gerbang
Data sekali pakai, dibuat lalu dibuang dalam satu fungsitidak — 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 panjangya — 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.