Skenario yang sering muncul di proyek nyata: sebuah objek berubah — suhu termostat naik, saldo rekening berkurang, data selesai dimuat — dan beberapa pihak lain perlu bereaksi: logger mencatat, alarm berbunyi, tombol UI menyala. Cara paling polos adalah memanggil semuanya langsung dari dalam method pengubah, tetapi itu menambat objek data pada detail tampilannya. MATLAB menyediakan pola events dan listeners untuk membalik arah: objek cukup mengumumkan bahwa sesuatu terjadi, dan pihak yang berkepentingan mendaftarkan diri untuk mendengar. Chapter ini membedah pola tersebut di MATLAB, lalu membangun padanan portabel yang bisa diuji di GNU Octave. Cakupannya:

  • tiga komponen: blok events, notify(), addlistener()
  • anatomi callback (src, evnt) dan custom event data
  • urutan eksekusi listener dan pelepasannya
  • padanan portabel: callback handle yang disimpan di property

Masalah: Siapa yang Harus Bereaksi Saat State Berubah #

Aturannya: komponen yang memiliki state sebaiknya tidak mengenal komponen yang memantau state itu. Mekanisme di balik layar: kalau Termostat.setSuhu() memanggil catatLogger() dan bunyiAlarm() secara langsung, setiap fitur baru (notifikasi email, grafik live) memaksa kita menyunting Termostat.m — class data berubah hanya karena penontonnya bertambah. Inilah masalah yang diselesaikan observer pattern: sumber peristiwa menyiarkan pesan, pendengar mendaftar dari luar, dan kedua pihak tidak saling menyebut. MATLAB mengekspresikan pola ini lewat infrastruktur bawaan handle class.

Tiga Komponen: events, notify, addlistener #

Versi MATLAB dari termostat kita memakai tiga komponen yang terverifikasi di dokumentasi resmi — blok events untuk menamai peristiwa, notify() untuk menyiarkan, addlistener() untuk mendaftarkan callback:

matlab
classdef Termostat < handle
    %TERMOSTAT Termostat dengan event SuhuBerubah (sintaks MATLAB).
    properties
        suhu = 25
    end
    events
        SuhuBerubah
    end
    methods
        function setSuhu(obj, nilaiBaru)
            lama = obj.suhu;
            obj.suhu = nilaiBaru;
            notify(obj, 'SuhuBerubah', ...
                SuhuBerubahData(lama, nilaiBaru));
        end
    end
end

Mari kita bedah baris-baris kode di atas:

  1. Baris 1 (classdef Termostat < handle): events hanya tersedia untuk handle class — dokumen resmi menempatkannya sebagai kemampuan yang diwarisi dari handle.
  2. Baris 6-8 (events ... SuhuBerubah ... end): mendeklarasikan nama event; hanya nama, tanpa parameter — data tambahan datang lewat event data.
  3. Baris 13-14 (notify(obj, 'SuhuBerubah', ...)): setelah suhu diperbarui, objek menyiarkan peristiwa SuhuBerubah ke semua listener terdaftar; argumen ketiga adalah payload opsional.
  4. Urutan baris 11-14: perubahan state dilakukan sebelum notify() — pendengar melihat kondisi yang sudah final.

Class pendengar tidak menyebut Termostat sama sekali; pendaftaran terjadi dari luar:

matlab
t = Termostat();
addlistener(t, 'SuhuBerubah', @(src, evt) ...
    fprintf('Logger: suhu %g -> %g\n', evt.lama, evt.baru));
t.setSuhu(28);

Pemanggilan t.setSuhu(28) memicu notify() di dalam class; MATLAB menyiarkan pesan ke seluruh listener SuhuBerubah, callback anonim terpanggil, dan Command Window menampilkan transisi 25 -> 28 — tanpa satu baris pun kode termostat yang menyebut logger.

Callback Listener dan Event Data #

Dokumentasi resmi menetapkan kontrak callback: setiap listener menerima minimal dua argumen — src (handle objek sumber peristiwa) dan evnt (object event.EventData atau subclass-nya). Payload bawaan event.EventData berisi dua property: EventName (nama peristiwa) dan Source (sumbernya). Data tambahan dikemas dengan mewarisi event.EventData:

matlab
classdef (ConstructOnLoad) SuhuBerubahData < event.EventData
    %SUHUBERUBAHDATA Payload event SuhuBerubah: suhu lama dan baru.
    properties
        lama
        baru
    end
    methods
        function data = SuhuBerubahData(lama, baru)
            data.lama = lama;
            data.baru = baru;
        end
    end
end

Ragam penulisan callback yang paling sering kita butuhkan:

Bentuk callbackKapan dipakai
@(src, evt) fprintf(...)anonymous function ringkas di titik pendaftaran
@namaFungsi atau @obj.metodefungsi biasa / method object tertentu
@(src, evt) f(src, evt, arg1)menyeret argumen tambahan dari konteks pendaftaran
@(~, ~) fmengabaikan src dan evt

Dua keputusan desain yang tersisa: siapa memegang umur listener dan event property bawaan. Fungsi addlistener() mengikat listener pada objek sumber — saat sumber dihapus, MATLAB ikut menghancurkan listener-nya; fungsi listener() membuat listener lepas yang usianya kita pegang sendiri (kodenya wajib menyimpan handle-nya agar tidak hilang). Kedua-duanya mengembalikan handle lh yang bisa dihapus kapan saja dengan delete(lh) untuk melepas pendaftaran. Selain event buatan, MATLAB menyediakan event property otomatis — PreSet/PostSet sebelum/sesudah property ditulis dan PreGet/PostGet di sekitar pembacaan — cukup dengan mendaftarkan listener dan menyalakan atribut SetObservable/GetObservable di property terkait.

Padanan Portabel: Callback di Property #

Inti pola events sesungguhnya sederhana: daftar function handle plus satu loop yang memanggil semuanya. Kedua bahan itu portabel penuh, sehingga Termostat.m yang kita kirim memakai desain padanan — property privat pendengar berperan sebagai daftar listener, method daftar() berperan sebagai addlistener(), dan loop di setSuhu() berperan sebagai notify():

matlab
classdef Termostat < handle
    %TERMOSTAT Padanan portabel pola events-listener MATLAB.
    %   MATLAB memakai events + notify + addlistener (lihat chapter);
    %   desain ini menyimpan function handle sebagai property sehingga
    %   berjalan di MATLAB maupun GNU Octave.

    properties (SetAccess = private)
        suhu        % suhu terkini (derajat)
    end

    properties (Access = private)
        pendengar   % cell array berisi function handle (src, data)
    end

    methods
        function obj = Termostat(suhuAwal)
            % Constructor: suhu awal berdefault 25.
            if nargin < 1
                suhuAwal = 25;
            end
            obj.suhu = suhuAwal;
            obj.pendengar = {};
        end

        function daftar(obj, callback)
            % daftar Mendaftarkan callback — padanan addlistener.
            %   Callback dipanggil sebagai callback(src, data) dengan
            %   data berupa struct berisi field lama dan baru.
            obj.pendengar{end+1} = callback;
        end

        function hentikan(obj)
            % hentikan Membuang seluruh callback — padanan delete(listener).
            obj.pendengar = {};
        end

        function setSuhu(obj, nilaiBaru)
            % setSuhu Mengubah suhu lalu memanggil semua pendengar.
            %   Padanan notify(obj, 'SuhuBerubah'): pemanggilan berurutan
            %   sesuai urutan pendaftaran.
            lama = obj.suhu;
            obj.suhu = nilaiBaru;
            if isempty(obj.pendengar)
                return   % tidak ada pendengar: tidak ada yang diberi tahu
            end
            data = struct('lama', lama, 'baru', nilaiBaru);
            for i = 1:numel(obj.pendengar)
                obj.pendengar{i}(obj, data);
            end
        end
    end
end

Skrip uji_termostat.m mendaftarkan dua pendengar lalu mengubah suhu dua kali:

matlab
t = Termostat(25);
t.daftar(@(src, data) ...
    fprintf('Logger : suhu %g -> %g\n', data.lama, data.baru));
t.daftar(@(src, data) ...
    fprintf('Alarm  : suhu kini %g derajat\n', data.baru));

t.setSuhu(28);
t.setSuhu(31);

t.hentikan();
t.setSuhu(20);
fprintf('setSuhu(20) tanpa pendengar: tidak ada callback berjalan\n');
fprintf('suhu terakhir: %g\n', t.suhu);

Output yang Dihasilkan:

text
Logger : suhu 25 -> 28
Alarm  : suhu kini 28 derajat
Logger : suhu 28 -> 31
Alarm  : suhu kini 31 derajat
setSuhu(20) tanpa pendengar: tidak ada callback berjalan
suhu terakhir: 20

Mari kita bedah baris-baris kode di atas:

  1. Baris 2-5 (t.daftar(...) dua kali): dua anonymous function didaftarkan — keduanya menerima (src, data), meniru kontrak (src, evt) milik MATLAB; struct data bermain peran event.EventData.
  2. Baris 7 (t.setSuhu(28)): satu perubahan memicu kedua pendengar — Logger selalu tampil sebelum Alarm karena loop di setSuhu() berjalan sesuai urutan pendaftaran.
  3. Baris 10 (t.hentikan()): padanan delete(lh) — setelah dipanggil, t.setSuhu(20) tetap mengubah suhu (baris terakhir membuktikan nilainya) tanpa satu callback pun berjalan.

Alur lengkapnya dari sisi termostat tergambar di bawah ini:

Diagram tersebut memperlihatkan letak decoupling: setSuhu() hanya tahu ada daftar pendengar — siapa mengisinya (logger, alarm, plot) bukan urusannya.

Kapan Memakai Pola Events #

SituasiPola yang tepat
UI/graph perlu memantau model tanpa model mengenal merekaevents / callback
Banyak komponen bereaksi pada satu perubahan stateevents / callback
Satu nilai hasil dibutuhkan pemanggilreturn biasa
Urutan langkah ketat dan diketahui (autentikasi lalu transaksi)panggilan method langsung
Aliran data dua arah (dialog isi formulir)method + return, bukan event

Pembatas terakhir: jangan mengubah state penting di dalam callback yang sama-sama mendengarkan satu event — kalau urutan eksekusi antar listener dijadikan syarat kebenaran program, desainnya sudah rapuh.

Ringkasan #

Events membalik arah dependensi: class data menamai peristiwa di blok events, menyiarkannya lewat notify(obj, 'Nama') dengan payload event.EventData (atau subclass-nya), dan pihak luar mendaftar lewat addlistener(obj, 'Nama', @(src, evt) ...) — callback selalu menerima sumber dan event data, listener bisa dilepas dengan delete(lh), dan event property PreSet/PostSet menambah jangkauan otomatis. Mekanisme ini MATLAB-ONLY (GNU Octave 10 belum mengimplementasikan notify/addlistener), sehingga berkas Termostat.m dan uji_termostat.m yang kita kirim memakai padanan portabel: daftar function handle di property, loop pemanggilan (src, data) di titik perubahan state, dan method hentikan() sebagai padanan delete(lh) — teruji penuh dan menampilkan perilaku yang kita targetkan dari pola aslinya.