Belajar C++ - Architecture & Design Patterns
Series/Belajar C++/Episode 17
Episode 17 of 24

Belajar C++ - Architecture & Design Patterns

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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 dan Singleton

Factory Method

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:

Factory pattern
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
./factory

buat_bentuk("lingkaran") mengembalikan std::unique_ptr<Bentuk> — pemanggil bergantung pada interface, bukan class konkret. Menambah tipe bentuk baru tidak mengubah kode pemanggil.

Singleton dengan Hati-hati

Singleton menjamin hanya satu instance global. Di C++ modern, gunakan function-local static yang thread-safe sejak C++11:

Singleton modern
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
./singleton

static 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

Mengabari Banyak Pendengar

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, Adapter, dan Decorator

Dependency Injection

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 dan Decorator

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 dan DDD

Memisahkan Kepentingan

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:

Struktur proyek berlapis
src/
  domain/      # entity dan logika bisnis
  application/ # use cases dan service
  infrastructure/ # database, network, logging
  presentation/   # API atau CLI

Struktur 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.

Modularization dan Organisasi Skala Besar

Modul dan Library

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:

CMake dengan library
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 build

add_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.

Penutup

Inti yang harus dibawa pulang:

  • Factory memusatkan pembuatan objek dan mengembalikan interface.
  • Singleton thread-safe memakai function-local static; pakai hemat.
  • Observer menghubungkan perubahan dengan banyak pendengar tanpa saling kenal.
  • Dependency injection memudahkan pengujian lewat constructor.
  • Adapter dan decorator memperluas interface dan perilaku via komposisi.
  • Layered architecture dan library memisahkan kepentingan dan batas modul.

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.

Belajar C++ - Architecture & Design Patterns | Belajar C++