Episode ini membahas arsitektur dan design pattern di C++: factory, singleton, dan observer, dependency injection, adapter, dan decorator, domain-driven design dan layered architecture, serta modularization untuk organisasi kode skala besar.

Setelah kalian menguasai sintaks dan STL, pertanyaan berikutnya adalah: bagaimana menyusun program yang besar agar tetap bisa dipelihara? Jawabannya adalah architecture dan design patterns — pola solusi yang terbukti untuk masalah yang berulang.
Episode 17 membahas pola paling relevan untuk C++: factory dan singleton untuk pembuatan objek, observer untuk komunikasi antar komponen, dependency injection dan adapter untuk mengurangi keterikatan, decorator untuk memperluas perilaku, serta layered architecture dan modularization untuk organisasi proyek skala besar.
Factory memusatkan logika pembuatan objek, sehingga pemanggil tidak perlu tahu class konkret mana yang dibuat. Di C++ modern, factory biasanya mengembalikan std::unique_ptr ke interface:
cat > factory.cpp <<'EOF'
#include <iostream>
#include <memory>
class Bentuk {
public:
virtual ~Bentuk() = default;
virtual void gambar() const = 0;
};
class Lingkaran : public Bentuk {
public:
void gambar() const override { std::cout << "Lingkaran\n"; }
};
class Persegi : public Bentuk {
public:
void gambar() const override { std::cout << "Persegi\n"; }
};
std::unique_ptr<Bentuk> buat_bentuk(const std::string& tipe) {
if (tipe == "lingkaran") {
return std::make_unique<Lingkaran>();
}
return std::make_unique<Persegi>();
}
int main() {
auto b = buat_bentuk("lingkaran");
b->gambar();
}
EOF
g++ -std=c++20 factory.cpp -o factory
./factorybuat_bentuk("lingkaran") mengembalikan std::unique_ptr<Bentuk> — pemanggil bergantung pada interface, bukan class konkret. Menambah tipe bentuk baru tidak mengubah kode pemanggil.
Singleton menjamin hanya satu instance global. Di C++ modern, gunakan function-local static yang thread-safe sejak C++11:
cat > singleton.cpp <<'EOF'
#include <iostream>
#include <string>
class Konfigurasi {
public:
static Konfigurasi& instance() {
static Konfigurasi cfg;
return cfg;
}
void set_nama(const std::string& n) { nama_ = n; }
std::string nama() const { return nama_; }
private:
Konfigurasi() = default;
std::string nama_ = "default";
};
int main() {
Konfigurasi::instance().set_nama("produksi");
std::cout << Konfigurasi::instance().nama() << "\n";
}
EOF
g++ -std=c++20 singleton.cpp -o singleton
./singletonstatic Konfigurasi cfg di dalam fungsi membuat instance yang diinisialisasi sekali dan thread-safe. Singleton berguna untuk konfigurasi global, tetapi pakai dengan bijak — state tersembunyi membuat unit testing sulit.
Observer memungkinkan satu objek memberi tahu banyak objek lain saat terjadi perubahan, tanpa mereka saling mengenal. Subjek menyimpan daftar observer, dan s.tambah(&log) mendaftarkan observer. Saat nilai berubah, s.set_nilai(42) memicu update di semua observer yang terdaftar. Perhatikan pointer observasi yang tidak memiliki — std::weak_ptr adalah alternatif yang lebih aman.
Dependency injection meneruskan dependency lewat constructor alih-alih membuatnya sendiri di dalam class. Constructor Layanan(std::shared_ptr<Penyimpan> p) menerima dependency dari luar. Ini memudahkan pengujian — saat unit test, ganti PenyimpanFile dengan implementasi palsu tanpa mengubah Layanan.
Adapter mengubah satu interface ke interface lain agar komponen yang tidak cocok tetap bisa bekerja sama. Decorator membungkus objek untuk menambah perilaku tanpa mengubah class aslinya. Keduanya memakai komposisi: bungkus objek dan teruskan panggilan sambil menambah logika.
Layered architecture membagi aplikasi menjadi lapisan: presentation, application, domain, dan infrastructure. Setiap lapisan hanya bergantung ke lapisan di bawahnya. Hasilnya kode lebih mudah diuji, dan perubahan teknologi di satu lapisan tidak mengguncang lapisan lain.
Domain-driven design (DDD) memusatkan logika bisnis di domain layer yang bebas dari framework dan database. Entity dan value object memodelkan aturan bisnis, sementara repository di infrastructure menangani penyimpanan. Aturan bisnis pun bisa diuji tanpa database.
Proyek besar terpecah menjadi direktori yang mencerminkan arsitektur:
src/
domain/ # entity dan logika bisnis
application/ # use cases dan service
infrastructure/ # database, network, logging
presentation/ # API atau CLIStruktur src/domain, src/application, src/infrastructure, src/presentation memisahkan lapisan sejak level direktori. Dependency selalu mengarah dari luar ke dalam — presentation boleh memakai domain, tetapi domain tidak boleh tahu tentang presentation.
Di skala besar, kode dipisah menjadi library dan executable. CMake mengelola ini lewat add_library dan target_link_libraries. Setiap library punya antarmuka publik kecil dan implementasi privat:
cat > CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.20)
project(arsitektur LANGUAGES CXX)
add_library(domain STATIC
src/domain/entitas.cpp
src/domain/repository.cpp)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE domain)
target_compile_features(domain PUBLIC cxx_std_20)
EOF
cmake -S . -B build
cmake --build buildadd_library(domain STATIC ...) membangun library statis dari file domain, dan target_link_libraries(app PRIVATE domain) menghubungkannya ke executable. target_compile_features(domain PUBLIC cxx_std_20) menetapkan standar C++20 untuk library dan pemakainya. Pola ini menjaga batas antar modul tetap tegas.
Tip
Dependency rule: dependency antar modul harus mengarah satu arah dan tidak boleh membentuk siklus. Siklus dependency adalah awal kekacauan di proyek besar.
Inti yang harus dibawa pulang:
Di episode 18 selanjutnya kita akan membahas system programming dan low-level integration — interaksi dengan system call dan API sistem operasi, pembuatan proses dengan fork dan pipe untuk IPC, memory mapped files, low-level I/O, serta dasar embedded systems dan pertimbangan bare-metal.