Fondasi model data dari Bagian 1 kini kita bungkus dalam class Perpustakaan yang mengelola tiga collection sekaligus. Bagian ini menuntaskan sisi paling sederhana dari sistem — menambah buku, mendaftarkan anggota, menghapus koleksi, dan mencari — tetapi justru di sini setiap invariant dari Bagian 1 pertama kali ditegakkan lewat kode. Setiap operasi mengembalikan nilai yang tidak bisa diabaikan: bool untuk sukses-gagal, pointer const untuk hasil pencarian, sehingga tidak ada satu pun jalur gagal yang bisa lewat diam-diam. Setelah bagian ini selesai, kita sudah bisa berinteraksi dengan perpustakaan secara penuh — kecuali meminjam, yang punya aturan jauh lebih banyak dan layak mendapat bagian tersendiri.
Menambah Buku dan Mendaftarkan Anggota #
Dua method pertama adalah gerbang masuk data. Method tambahBuku menjaga keunikan ISBN sebelum satu pun buku masuk vector:
// ---------- pengelolaan koleksi ----------
bool tambahBuku(const Buku& buku) {
if (temukanBuku(buku.isbn) != nullptr) {
return false; // invarian: ISBN harus unik
}
daftarBuku_.push_back(buku);
return true;
}daftarAnggota memakai pola yang sama untuk id anggota, dengan satu gerbang tambahan di depan:
bool daftarAnggota(const Anggota& anggota) {
if (anggota.id <= 0 || temukanAnggota(anggota.id) != nullptr) {
return false; // invarian: id positif dan unik
}
daftarAnggota_.push_back(anggota);
return true;
}Pola keduanya identik dan sengaja dibuat demikian. Cari dulu apakah kunci sudah dipakai — temukanBuku membandingkan ISBN, temukanAnggota membandingkan id — dan hanya bila tidak ada yang bertabrakan, push_back dijalankan. daftarAnggota menambahkan satu gerbang lagi: anggota.id <= 0 ditolak, karena id nol atau negatif bukan identifier yang bisa dipakai Peminjaman dengan bermakna. Perhatikan bahwa pemeriksaan terjadi sebelum perubahan state, bukan sesudahnya; tidak ada situasi di mana buku duplikat masuk vector lalu dibuang lagi. Bila gerbang ditolak, method cukup mengembalikan false — pemanggil di prosesBaris yang menerjemahkannya menjadi pesan galat yang ramah.
Menghapus dengan Syarat #
Menghapus buku tidak boleh semata-mata mengeluarkan elemen dari vector. Sebuah buku yang sedang dipinjam adalah pihak yang disebut dalam catatan Peminjaman aktif — mengeluarkannya membuat catatan itu menunjuk ke buku yang tidak ada:
bool hapusBuku(const std::string& isbn) {
// Buku yang sedang dipinjam tidak boleh lepas dari koleksi.
if (temukanPeminjamanAktif(isbn) != nullptr) {
return false;
}
const std::size_t jumlahSebelum = daftarBuku_.size();
daftarBuku_.erase(
std::remove_if(daftarBuku_.begin(), daftarBuku_.end(),
[&](const Buku& b) { return b.isbn == isbn; }),
daftarBuku_.end());
return daftarBuku_.size() < jumlahSebelum;
}Tiga hal layak dibedah:
- Gerbang
temukanPeminjamanAktif(isbn)— versi satu-argumen dari helper ini mencari catatan berstatusDipinjamuntuk ISBN tersebut, tanpa peduli siapa peminjamnya. Bila ada satu saja, penghapusan ditolak dan invariant “buku yang sedang dipinjam tidak bisa dihapus” tetap berdiri. - Idiom erase-remove —
std::remove_ifsebenarnya tidak menghapus apa pun; ia memindahkan elemen yang lolos seleksi ke depan vector dan mengembalikan iterator ke posisi sisa yang tidak terpakai.eraseyang menerima rentang itu yang benar-benar memotong. Memanggilremove_ifsaja membuat ukuran vector tetap sama — kesalahan yang sangat umum pada pemula. - Nilai kembali dari pengukuran, bukan asumsi — method mencatat
jumlahSebelumlalu membandingkan ukuran sesudahnya. Bila tidak ada elemen yang cocok, ukuran tidak berubah dan method melaporkanfalse; pemanggil tidak perlu menebak apakah “hapus” benar-benar menghapus.
Pencarian Judul dan Penulis Tanpa Peduli Huruf #
Pengguna tidak mengetik “Siti Rahmawati” dengan kapitalisasi yang selalu benar. Karena itu pencarian judul dan penulis kita normalisasi dulu ke huruf kecil, lalu dicocokkan sebagai substring:
// Pencarian judul dan penulis: tidak peka huruf besar-kecil dan
// boleh cocok sebagian (substring).
std::vector<Buku> cariByJudul(const std::string& kunci) const {
return saring(kunci, [](const Buku& b) { return b.judul; });
}
std::vector<Buku> cariByPenulis(const std::string& kunci) const {
return saring(kunci, [](const Buku& b) { return b.penulis; });
}Kedua function memakai satu helper privat bernama saring yang menerima lambda sebagai projector — function kecil yang menyebut field mana yang dilihat:
template <typename Pengambil>
std::vector<Buku> saring(const std::string& kunci, Pengambil ambil) const {
const std::string kunciKecil = kecilkan(kunci);
std::vector<Buku> hasil;
for (const Buku& b : daftarBuku_) {
if (kecilkan(ambil(b)).find(kunciKecil) != std::string::npos) {
hasil.push_back(b);
}
}
return hasil;
}kecilkan dipanggil pada dua sisi perbandingan — kunci pencarian dan field buku — sehingga SITI, siti, dan Siti sama-sama menemukan buku tulisan Siti Rahmawati. Kecocokan berupa substring lewat std::string::find: kata c++ menemukan “Pemrograman C++ Modern” walau hanya menempel di tengah judul. Template Pengambil membuat saring bekerja untuk judul maupun penulis tanpa menyalin logika; satu function yang diuji, dua pemakaian. Ini pola yang sama dengan temukan di proyek inventaris: menulis sekali, menjaga konsistensi di mana-mana.
Mencoba CRUD Koleksi #
Kompilasi program utuh, lalu jalankan sesi yang menyentuh seluruh operasi bagian ini — termasuk lima percobaan yang harus ditolak:
g++ -std=c++23 -Wall -Wextra perpustakaan.cpp -o perpustakaan
printf 'BUKU|978-6021|Pemrograman C++ Modern|Budi Santoso|2\nBUKU|978-6022|Algoritma dan Struktur Data|Siti Rahmawati|1\nBUKU|978-6023|Clean Code|Dewi Lestari|3\nBUKU|978-6021|Edisi Revisi|Penulis Lain|5\nBUKU|978-6024|Struktur Data Dasar|Budi Santoso|abc\nANGGOTA|1|Budi\nANGGOTA|1|Siti\nANGGOTA|-2|Dewi\nLAPOR\nCARI|c++\nCARI|SITI\nCARI|python\nHAPUS|978-6023\nHAPUS|978-9999\nLAPOR\n' | ./perpustakaanOutput yang Dihasilkan:
ok: buku "Pemrograman C++ Modern" masuk koleksi
ok: buku "Algoritma dan Struktur Data" masuk koleksi
ok: buku "Clean Code" masuk koleksi
gagal: ISBN 978-6021 sudah terdaftar
gagal: stok bukan bilangan bulat non-negatif: "abc"
ok: anggota Budi (id 1) terdaftar
gagal: id 1 sudah terdaftar
gagal: id anggota tidak valid: "-2"
ISBN Judul Penulis Stok
----------------------------------------------------------------------
978-6021 Pemrograman C++ Modern Budi Santoso 2
978-6022 Algoritma dan Struktur Data Siti Rahmawati 1
978-6023 Clean Code Dewi Lestari 3
Peminjaman aktif: 0
cari "c++": 1 hasil
- 978-6021 | Pemrograman C++ Modern | Budi Santoso | stok 2
cari "SITI": 1 hasil
- 978-6022 | Algoritma dan Struktur Data | Siti Rahmawati | stok 1
cari "python": 0 hasil
ok: buku dengan ISBN 978-6023 dikeluarkan dari koleksi
gagal: buku tidak bisa dihapus (tidak ada atau sedang dipinjam)
ISBN Judul Penulis Stok
----------------------------------------------------------------------
978-6021 Pemrograman C++ Modern Budi Santoso 2
978-6022 Algoritma dan Struktur Data Siti Rahmawati 1
Peminjaman aktif: 0Sesi ini memperlihatkan setiap gerbang bekerja sekali jalan. ISBN 978-6021 kedua ditolak karena duplikat, stok abc tertahan pengurai angka sebelum menyentuh koleksi, id 1 kedua ditolak karena sudah terdaftar, dan id -2 tertahan di gerbang nilai positif. Pencarian berjalan dua arah: kunci c++ cocok dengan judul, kunci SITI cocok dengan penulis walau kapitalisasinya berbeda, dan python jujur melapor nol hasil. Penghapusan menutup sesi — 978-6023 lepas dari koleksi karena tidak ada catatan pinjamannya, sementara 978-9999 ditolak karena memang tidak pernah ada. LAPOR di akhir menampilkan koleksi yang tinggal dua baris, membuktikan bahwa state hanya berubah lewat gerbang yang memeriksa.
Koleksi kini bisa ditambah, dicari, dan dihapus dengan aman, tetapi perpustakaan yang tidak bisa meminjam bukan perpustakaan. Bagian 3 menerapkan alur peminjaman — lima gerbang yang memutuskan apakah sebuah pinjaman boleh terjadi — beserta pengembalian yang memulihkan stok.
Rujukan resmi: std::remove_if (cppreference) , std::string::find (cppreference) .