Published on

OOP Sebagai Alat Mengontrol Dependency (Bukan Gaya-Gayaan)

Authors

πŸ“˜ Artikel 2: OOP Sebagai Alat Mengontrol Dependency (Bukan Gaya-Gayaan)

Posisi: Artikel 2 dari 8 Domain Keputusan: Dependency Control Status Lock: πŸ”’ Mengunci aturan dependency & global state Entry Context: IndustrialNode/IndustrialNode.ino



1. Problem Reality

Melanjutkan Artikel 1:

Firmware menjadi spaghetti bukan karena C atau Arduino. Masalah paling merusak adalah:

  • Global state lintas modul
  • Fungsi saling panggil tanpa boundary
  • ISR memanggil logic langsung
  • Callback komunikasi mengontrol hardware
  • Tidak ada ownership state

Contoh klasik di firmware Arduino:

bool relayState = false;

void onMqttMessage(...) {
    relayState = true;
    digitalWrite(5, HIGH);
}

void IRAM_ATTR buttonISR() {
    relayState = !relayState;
}

void loop() {
    if (temperature > 30) {
        relayState = true;
    }
}

Relay bisa diubah dari:

  • Loop
  • ISR
  • MQTT callback

Tidak ada pemilik.

Akibat produksi:

  • Perubahan kecil β†’ efek domino
  • Refactor sulit tanpa risiko
  • Race sulit dianalisis
  • Bug menyebar lintas domain

Masalahnya bukan bahasa.

Masalahnya:

Tidak ada kontrol dependency.


2. Root Cause Analysis

Akar strukturalnya sederhana.

2.1 State Tidak Memiliki Owner

Global mutable adalah dependency implicit.

Semua modul bisa membaca dan menulis.

Tidak ada batas.


2.2 Dependency Tanpa Direction

Dependency aktual sering terlihat seperti ini:

MQTT callback β†’ relay_set()
ISR β†’ relay_set()
Main loop β†’ relay_set()

Atau lebih buruk:

WiFi event β†’ mode
Mode β†’ relay
Relay β†’ publish telemetry
Telemetry β†’ MQTT
MQTT callback β†’ mode

Dependency menjadi cyclic.


πŸ”Ž Visualisasi Dependency Chaos

Image

Image

Tanpa ownership dan direction rule, setiap modul menjadi pusat dari modul lain.

Inilah sumber efek domino.


2.3 Modul Bukan Unit Independen

Jika satu modul membutuhkan modul lain melalui global:

  • Ia tidak bisa diuji terpisah.
  • Ia tidak bisa direfactor aman.
  • Ia tidak punya kontrak eksplisit.

Dependency harus terlihat di constructor, bukan tersembunyi.


3. Design Principle (Rule yang Dikunci)

Artikel ini mengunci fondasi dependency discipline.


πŸ”’ Rule 1 β€” No Global Mutable State

Semua state runtime harus berada di dalam class.

Diperbolehkan:

  • constexpr
  • compile-time config
  • macro config

Dilarang:

  • bool globalState;
  • int mode;
  • object global mutable

πŸ”’ Rule 2 β€” Encapsulation Mandatory

Setiap peripheral dan domain logic:

  • Dibungkus dalam class
  • State private
  • Akses melalui method

State tidak boleh public.


πŸ”’ Rule 3 β€” Composition > Inheritance

Firmware dibangun dengan composition.

Contoh:

  • ActuatorService memiliki RelayDriver
  • SensorService memiliki TempSensorDriver

Inheritance hanya jika benar-benar perlu. Default approach: composition.


πŸ”’ Rule 4 β€” No Singleton

Tidak boleh:

Logger::instance()
WiFiManager::getInstance()

Instance dibuat di:

IndustrialNode.ino

Dan dependency diberikan via constructor.


πŸ”’ Rule 5 β€” Explicit Dependency Injection

Jika A membutuhkan B:

  • B diberikan melalui constructor
  • Tidak diambil dari global

Contoh:

class ActuatorService {
public:
    explicit ActuatorService(RelayDriver& relay)
        : relay_(relay) {}
private:
    RelayDriver& relay_;
};

Dependency terlihat jelas.


Setelah Artikel 2:

  • ❌ Tidak boleh kembali ke global mutable
  • ❌ Tidak boleh introduce singleton
  • ❌ Tidak boleh ambil dependency implicit

Ini tidak boleh berubah di artikel berikutnya.


4. Implementation Pattern (ESP32 Arduino Context)

Kita refactor contoh spaghetti menjadi terstruktur.


4.1 Driver Layer (Hardware Boundary)

class RelayDriver {
public:
    explicit RelayDriver(int pin)
        : pin_(pin), state_(false) {}

    void init() {
        pinMode(pin_, OUTPUT);
        digitalWrite(pin_, LOW);
    }

    void set(bool on) {
        state_ = on;
        digitalWrite(pin_, on ? HIGH : LOW);
    }

    bool state() const {
        return state_;
    }

private:
    int pin_;
    bool state_;
};

State relay sekarang:

  • Private
  • Tidak bisa diubah sembarang tempat
  • Hanya melalui API

4.2 Service Layer (Logic Boundary)

class ActuatorService {
public:
    explicit ActuatorService(RelayDriver& relay)
        : relay_(relay) {}

    void setRelay(bool on) {
        relay_.set(on);
    }

private:
    RelayDriver& relay_;
};

Service:

  • Tidak tahu GPIO
  • Tidak tahu MQTT
  • Tidak tahu ISR

Ia hanya mengontrol domain actuator.


4.3 IndustrialNode.ino (Composition Root)

#include "drv_RelayDriver.h"
#include "svc_ActuatorService.h"

RelayDriver relay(5);
ActuatorService actuator(relay);

void setup() {
    relay.init();
}

void loop() {
    // nanti akan didelegasikan ke App layer (Artikel 4)
}

Semua instance dibuat di satu tempat.

Tidak ada singleton. Tidak ada global mutable state lintas domain.


πŸ”Ž Visualisasi Ownership Model

Image

Image

Image

Ownership sekarang:

  • RelayDriver memiliki state relay
  • ActuatorService memiliki referensi ke RelayDriver
  • IndustrialNode.ino memiliki semua instance

Dependency menjadi directional, bukan cyclic.


5. Constraint & Embedded Impact

RAM Impact

  • Class menambah metadata kecil.
  • Overhead sangat minimal dibanding risiko global chaos.
  • State lebih terkonsolidasi.

Flash Impact

  • Sedikit naik karena struktur class.
  • Diterima demi clarity & maintainability.

Stack Impact

  • Tidak ada perubahan signifikan.
  • Constructor injection tidak mahal.

Determinism Impact

  • State owner jelas.
  • Race lebih mudah dianalisis.
  • Blast radius perubahan mengecil.

OOP yang disiplin bukan mahal. Yang mahal adalah chaos.


6. Failure Scenario

Scenario 1 β€” ISR Mengubah Relay

Sebelumnya:

relayState = !relayState;

Sekarang:

ISR tidak punya akses ke state private.

Ia harus melalui service atau event mechanism (dibahas di Artikel 5).

Blast radius dibatasi.


Scenario 2 β€” MQTT Callback Mengubah Mode Global

Sebelumnya:

mode = AUTO;

Sekarang:

Tidak ada global mode.

Callback harus berinteraksi melalui layer yang jelas.

Guard bisa ditambahkan di satu tempat.


Encapsulation membatasi kerusakan.


7. Anti-Pattern

  • ❌ Global object mutable dengan external linkage
  • ❌ Singleton logger / WiFi manager
  • ❌ Class hanya kumpulan static function
  • ❌ Inheritance untuk β€œfuture extensibility”
  • ❌ Public member variable

Dampak:

  • Hidden dependency
  • Race condition
  • Refactor sulit
  • Testing mustahil

8. Freeze Point

Setelah Artikel 2, keputusan berikut dianggap final:

  • Tidak ada global mutable state.
  • Tidak ada singleton.
  • Semua dependency explicit via constructor.
  • State hanya di dalam class.
  • Composition adalah default approach.

Artikel berikutnya tidak boleh melanggar ini.


9. Engineering Checklist

  • Apakah ada global bool/int runtime?
  • Apakah ada static object global yang mutable?
  • Apakah dependency diambil tanpa constructor?
  • Apakah class expose state public?
  • Apakah ada singleton?

Jika satu saja β€œya”, melanggar Artikel 2.


10. Summary (5 Bullet Maksimal)

  • Dependency chaos berasal dari global state.
  • OOP dipakai untuk mengontrol ownership.
  • Encapsulation adalah boundary, bukan gaya.
  • Composition lebih aman untuk embedded.
  • Setelah ini, global mutable state dilarang.

Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.