- 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
- π Artikel 2: OOP Sebagai Alat Mengontrol Dependency (Bukan Gaya-Gayaan)
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


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



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.