Nesne İlişkileri — Composition, Aggregation, Association, Dependency
Programlama öğrenirken genelde sınıfları (class) tek başına yazarız. Ama gerçek dünyada nesneler tek başına yaşamaz — birbirleriyle ilişki kurar, birbirlerine bağımlı olur, birbirlerini kullanır. İşte nesne yönelimli programlamanın (OOP) en kritik konularından biri de bu ilişkilerin doğru modellenmesidir.
Bu derste dört temel nesne ilişkisini öğreneceksin: Composition, Aggregation, Association ve Dependency. Bunları anlamak, sadece kod yazmak için değil, yazılım tasarımını (software design) düşünmek için de çok önemli. Hazırsan başlayalım.
Büyük Resim: Nesneler Arası İlişki Türleri
Bir yazılım projesinde sınıflar birbirleriyle farklı şekillerde etkileşir. Bu etkileşimlerin her birinin bir "gücü" var — bazıları çok sıkı bağ kurar, bazıları ise çok gevşek.
En güçlüden en zayıfa doğru sıralayalım:
Composition (Bileşim) — En güçlü bağ. "Parça bütünsüz yaşayamaz."
Aggregation (Kümeleme) — Güçlü ama esnek. "Parça bütünsüz de yaşar."
Association (İlişkilendirme) — İki bağımsız nesne birbirini tanır.
Dependency (Bağımlılık) — En zayıf. "Anlık kullanım, sonra yollar ayrılır."
Bu dört kavram, nesne yönelimli tasarımın (OOD — Object-Oriented Design) temel taşlarıdır. Hepsini sırayla, örneklerle göreceğiz.
Composition — Güçlü Sahiplik ("Has-A" İlişkisi)
Analoji: Araba ve Motor 🚗
Bir araba düşün. Arabanın bir motoru var. Motor, arabanın parçası. Arabayı hurdaya gönderdiğinde motor da gider. Motor tek başına bir anlam ifade etmez — o arabanın motorudur. Başka bir arabaya takamazsın (en azından tasarım olarak).
İşte composition tam olarak bu. Bir nesne, başka bir nesneyi sahiplenir ve o nesnenin yaşam döngüsünü (lifecycle) kontrol eder. Sahip nesne (owner) yok edildiğinde, parça nesne de yok edilir.
Temel Kurallar
Parça (part), bütünün (whole) yaşam döngüsüne bağlıdır
Bütün oluşturulduğunda parça da oluşturulur
Bütün yok edildiğinde parça da yok edilir
Parça başka bir bütüne ait olamaz (münhasır sahiplik)
Genellikle parça, bütünün içinde değer olarak (by value) tutulur
Kod Örneği: Car ve Engine
#include <iostream>
#include <string>
class Engine {
private:
int horsepower;
std::string type;
public:
Engine(int hp, const std::string& t)
: horsepower(hp), type(t) {
std::cout << "Engine olusturuldu: " << type
<< " (" << horsepower << " HP)" << std::endl;
}
~Engine() {
std::cout << "Engine yok edildi: " << type << std::endl;
}
void start() const {
std::cout << type << " motor calisiyor..." << std::endl;
}
int getHorsepower() const { return horsepower; }
};
class Car {
private:
std::string brand;
Engine engine; // Composition: deger olarak tutuluyor!
public:
Car(const std::string& b, int hp, const std::string& engineType)
: brand(b), engine(hp, engineType) { // Engine burada olusturuluyor
std::cout << brand << " arabasi olusturuldu." << std::endl;
}
~Car() {
std::cout << brand << " arabasi yok edildi." << std::endl;
// Engine otomatik olarak yok edilecek (deger olarak tutuldugundan)
}
void drive() const {
std::cout << brand << " hareket ediyor!" << std::endl;
engine.start();
}
};
int main() {
{
Car myCar("Toyota", 150, "V4");
myCar.drive();
std::cout << "--- Blok sonu ---" << std::endl;
} // myCar yok edilir → engine de otomatik yok edilir
return 0;
}Çıktı:
Engine olusturuldu: V4 (150 HP)
Toyota arabasi olusturuldu.
Toyota hareket ediyor!
V4 motor calisiyor...
--- Blok sonu ---
Toyota arabasi yok edildi.
Engine yok edildi: V4Dikkat et: Engine nesnesi Car içinde değer olarak (by value) tutuluyor. new ile heap'te oluşturulmadı. Bu, composition'ın en temiz implementasyonu. Car yok edildiğinde Engine de otomatik olarak yok ediliyor — hiçbir şey yapmana gerek yok.
Composition'da unique_ptr Kullanımı
Bazen parça nesnesini heap'te oluşturmak isteyebilirsin (örneğin polimorfizm için). Bu durumda std::unique_ptr kullanmak composition'ı mükemmel ifade eder:
#include <iostream>
#include <memory>
#include <string>
class Screen {
private:
int width, height;
public:
Screen(int w, int h) : width(w), height(h) {
std::cout << "Screen olusturuldu: " << width << "x" << height << std::endl;
}
~Screen() {
std::cout << "Screen yok edildi." << std::endl;
}
void display(const std::string& text) const {
std::cout << "[" << width << "x" << height << "] " << text << std::endl;
}
};
class Laptop {
private:
std::string model;
std::unique_ptr<Screen> screen; // Composition: unique_ptr ile
public:
Laptop(const std::string& m, int w, int h)
: model(m), screen(std::make_unique<Screen>(w, h)) {
std::cout << model << " laptop olusturuldu." << std::endl;
}
// unique_ptr sayesinde destructor'a gerek yok,
// otomatik olarak Screen'i siler
void showMessage(const std::string& msg) const {
std::cout << model << ": ";
screen->display(msg);
}
};
int main() {
Laptop myLaptop("ThinkPad", 1920, 1080);
myLaptop.showMessage("Merhaba Dunya!");
return 0;
}unique_ptr kullandığında sahiplik (ownership) çok net: Laptop yok edildiğinde Screen de otomatik silinir. Ve unique_ptr kopyalanamadığı için bir Screen'in iki farklı Laptop'a ait olması imkansız. Tam da composition'ın istediği şey!
💡 İpucu: Composition implementasyonu için ilk tercih: değer olarak tutmak. Polimorfizm gerekiyorsa:
std::unique_ptr. Raw pointer (new/delete) kullanma — hem tehlikeli hem de niyetini belirsiz bırakır.
Aggregation — Zayıf Sahiplik ("Has-A" Ama Bağımsız)
Analoji: Üniversite ve Öğrenci 🎓
Bir üniversite düşün. Üniversitede öğrenciler var. Ama üniversite kapansa öğrenciler ölmez! Öğrenci, üniversiteden bağımsız olarak var olabilir. Başka bir üniversiteye geçebilir, mezun olabilir, kendi işini kurabilir.
İşte aggregation bu. Bir nesne başka nesneleri "içerir" ama onların yaşam döngüsünü kontrol etmez. Nesneler bağımsızdır, sadece bir arada bulunurlar.
Temel Kurallar
Parça, bütünden bağımsız var olabilir
Bütün yok edildiğinde parçalar yok edilmez
Bir parça birden fazla bütüne ait olabilir
Genellikle pointer veya reference ile tutulur
Destructor parçaları silmez
Kod Örneği: Department ve Student
#include <iostream>
#include <vector>
#include <string>
class Student {
private:
std::string name;
int id;
public:
Student(const std::string& n, int i) : name(n), id(i) {
std::cout << "Student olusturuldu: " << name << std::endl;
}
~Student() {
std::cout << "Student yok edildi: " << name << std::endl;
}
std::string getName() const { return name; }
int getId() const { return id; }
};
class Department {
private:
std::string name;
std::vector<Student*> students; // Aggregation: pointer ile!
public:
Department(const std::string& n) : name(n) {
std::cout << "Department olusturuldu: " << name << std::endl;
}
~Department() {
std::cout << "Department yok edildi: " << name << std::endl;
// students'lari SILMIYORUZ! Onlar bizim sorumlulugumuz degil.
// Sadece pointer listesini temizliyoruz.
students.clear();
}
void addStudent(Student* student) {
students.push_back(student);
std::cout << student->getName() << " -> " << name
<< " bolumune eklendi." << std::endl;
}
void listStudents() const {
std::cout << name << " bolumu ogrencileri:" << std::endl;
for (const auto* s : students) {
std::cout << " - " << s->getName()
<< " (ID: " << s->getId() << ")" << std::endl;
}
}
};
int main() {
// Ogrenciler bagimsiz olarak olusturuluyor
Student* ali = new Student("Ali", 101);
Student* ayse = new Student("Ayse", 102);
Student* mehmet = new Student("Mehmet", 103);
{
Department cs("Bilgisayar Muhendisligi");
Department math("Matematik");
cs.addStudent(ali);
cs.addStudent(ayse);
math.addStudent(ayse); // Ayse iki bolumde birden!
math.addStudent(mehmet);
cs.listStudents();
math.listStudents();
std::cout << "--- Departmanlar yok edilecek ---" << std::endl;
}
// Departmanlar yok edildi ama ogrenciler hala yasiyorlar!
std::cout << ali->getName() << " hala burada!" << std::endl;
std::cout << ayse->getName() << " hala burada!" << std::endl;
// Ogrencileri biz temizliyoruz
delete ali;
delete ayse;
delete mehmet;
return 0;
}Burada kritik noktayı gördün mü? Department yok edildiğinde Student nesneleri hayatta kalıyor. Hatta ayse iki departmana birden eklenmiş — composition'da bu imkansız olurdu.
⚠️ Dikkat: Aggregation'da raw pointer kullanıyorsan, sahipliğin (ownership) nerede olduğunu çok iyi dokümante et. Kim oluşturuyor, kim siliyor? Bu net olmazsa memory leak veya double-free kaçınılmaz. Modern C++'ta
std::shared_ptrkullanmak bu sorunu büyük ölçüde çözer.
Aggregation'da shared_ptr ile Güvenli Versiyon
#include <iostream>
#include <vector>
#include <memory>
#include <string>
class Player {
private:
std::string name;
public:
Player(const std::string& n) : name(n) {
std::cout << "Player olusturuldu: " << name << std::endl;
}
~Player() {
std::cout << "Player yok edildi: " << name << std::endl;
}
std::string getName() const { return name; }
};
class Team {
private:
std::string teamName;
std::vector<std::shared_ptr<Player>> players; // shared_ptr ile aggregation
public:
Team(const std::string& n) : teamName(n) {}
~Team() {
std::cout << "Team yok edildi: " << teamName << std::endl;
// shared_ptr'ler otomatik temizlenir
// Ama Player'lar sadece son referans silindiginde yok edilir
}
void addPlayer(std::shared_ptr<Player> p) {
players.push_back(p);
}
void showRoster() const {
std::cout << teamName << " kadrosu:" << std::endl;
for (const auto& p : players) {
std::cout << " - " << p->getName()
<< " (ref count: " << p.use_count() << ")" << std::endl;
}
}
};
int main() {
auto ronaldo = std::make_shared<Player>("Ronaldo");
auto messi = std::make_shared<Player>("Messi");
{
Team teamA("Takim A");
Team teamB("Takim B");
teamA.addPlayer(ronaldo);
teamA.addPlayer(messi);
teamB.addPlayer(messi); // Messi iki takimda!
teamA.showRoster();
teamB.showRoster();
}
// Takimlar yok edildi
// Ronaldo ve Messi hala yasiyor (main'deki shared_ptr sayesinde)
std::cout << ronaldo->getName() << " hala aktif!" << std::endl;
std::cout << messi->getName() << " hala aktif!" << std::endl;
return 0;
// Burada ronaldo ve messi'nin shared_ptr'leri scope'dan cikar
// ve Player nesneleri yok edilir
}shared_ptr kullanmak aggregation'ı hem güvenli hem de niyetini açık hale getiriyor. Referans sayacı (reference count) sayesinde nesne, son sahip de bıraktığında otomatik silinir.
Composition vs Aggregation — Farkı Netleştirelim
Bu iki kavram sürekli karıştırılır. Bir tablo ile netleştirelim:
| Özellik | Composition | Aggregation |
|---|---|---|
| İlişki gücü | Güçlü (sıkı bağ) | Zayıf (gevşek bağ) |
| Yaşam döngüsü | Bütüne bağlı | Bağımsız |
| Sahiplik | Münhasır (exclusive) | Paylaşılabilir |
| Tutma şekli | Değer veya unique_ptr | Pointer, reference, shared_ptr |
| Bütün silinince | Parça da silinir | Parça hayatta kalır |
| UML sembolü | ◆ (dolu elmas) | ◇ (boş elmas) |
| Analoji | Araba-Motor | Üniversite-Öğrenci |
İkisi de "has-a" ilişkisi ama birinde sahiplik güçlü, diğerinde zayıf. Kararını verirken kendine şunu sor:
"Bütün yok edildiğinde, parçanın tek başına var olması mantıklı mı?"
Evet → Aggregation
Hayır → Composition
Association — Bağımsız İlişki
Analoji: Doktor ve Hasta 🏥
Bir doktor düşün. Doktorun hastaları var, hastaların doktoru var. Ama doktor hastanın parçası değil, hasta da doktorun parçası değil. İkisi bağımsız yaşar, sadece birbirlerini tanır ve etkileşim kurar.
Association'da iki nesne birbirine referans tutar ama hiçbiri diğerinin yaşam döngüsünü kontrol etmez. İkisi de bağımsız oluşturulur ve bağımsız yok edilir.
Temel Kurallar
İki nesne birbirini tanır ama sahiplik yoktur
Her iki nesne de bağımsız yaşar
İlişki çift yönlü olabilir (A → B ve B → A)
Genellikle pointer veya reference ile birbirlerine erişir
Hiçbir taraf diğerinin yaşam döngüsünü yönetmez
Kod Örneği: Doctor ve Patient
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
class Patient; // Forward declaration
class Doctor {
private:
std::string name;
std::string specialty;
std::vector<Patient*> patients;
public:
Doctor(const std::string& n, const std::string& s)
: name(n), specialty(s) {}
std::string getName() const { return name; }
std::string getSpecialty() const { return specialty; }
void addPatient(Patient* p) {
patients.push_back(p);
}
void removePatient(Patient* p) {
patients.erase(
std::remove(patients.begin(), patients.end(), p),
patients.end()
);
}
void listPatients() const; // Patient tanimlandiktan sonra implement edilecek
};
class Patient {
private:
std::string name;
int age;
std::vector<Doctor*> doctors;
public:
Patient(const std::string& n, int a) : name(n), age(a) {}
std::string getName() const { return name; }
void addDoctor(Doctor* d) {
doctors.push_back(d);
}
void listDoctors() const {
std::cout << name << "'in doktorlari:" << std::endl;
for (const auto* d : doctors) {
std::cout << " - Dr. " << d->getName()
<< " (" << d->getSpecialty() << ")" << std::endl;
}
}
};
// Doctor::listPatients implementasyonu
void Doctor::listPatients() const {
std::cout << "Dr. " << name << "'in hastalari:" << std::endl;
for (const auto* p : patients) {
std::cout << " - " << p->getName() << std::endl;
}
}
// Iliski kurma yardimci fonksiyonu
void createAppointment(Doctor& doc, Patient& pat) {
doc.addPatient(&pat);
pat.addDoctor(&doc);
std::cout << pat.getName() << " <-> Dr. " << doc.getName()
<< " randevusu olusturuldu." << std::endl;
}
int main() {
Doctor drAli("Ali Yilmaz", "Kardiyoloji");
Doctor drAyse("Ayse Demir", "Noroloji");
Patient hasta1("Mehmet", 45);
Patient hasta2("Zeynep", 32);
createAppointment(drAli, hasta1);
createAppointment(drAli, hasta2);
createAppointment(drAyse, hasta1); // Mehmet iki doktora gidiyor
std::cout << std::endl;
drAli.listPatients();
drAyse.listPatients();
std::cout << std::endl;
hasta1.listDoctors();
hasta2.listDoctors();
return 0;
}Dikkat et: ne Doctor ne de Patient diğerini oluşturuyor veya siliyor. İkisi de bağımsız yaşıyor. createAppointment fonksiyonu ikisi arasındaki ilişkiyi kuruyor, bu kadar.
Association vs Aggregation Farkı
İkisi de pointer/reference kullanır, ikisinde de sahip nesne parçayı silmez. Peki fark ne?
Aggregation: Bir taraf "bütün" rolünde, diğeri "parça" rolünde. Hiyerarşik bir ilişki var.
Association: İki taraf eşit. Hiyerarşi yok, sadece tanışıklık var.
Department ve Student → Aggregation (Department, Student'ları "içerir") Doctor ve Patient → Association (ikisi de eşit, sadece birbirini tanır)
Dependency — En Zayıf İlişki
Analoji: Taksi ve Yolcu 🚕
Taksici yolcuyu bir yerden alır, bir yere bırakır. Yolculuk bittiğinde ikisi arasında hiçbir bağ kalmaz. Taksici yolcunun telefonunu tutmaz, yolcu taksicinin plakasını hatırlamaz. Anlık, geçici bir etkileşim.
Dependency, bir nesnenin başka bir nesneyi geçici olarak kullanmasıdır. Kalıcı bir referans tutulmaz, sadece fonksiyon parametresi, lokal değişken veya dönüş değeri olarak anlık kullanım söz konusudur.
Temel Kurallar
En zayıf ilişki türü
Kalıcı referans veya pointer tutulmaz
Genellikle fonksiyon parametresi olarak geçer
Kullanım bittikten sonra ilişki sona erer
"Uses" ilişkisi olarak da bilinir
Kod Örneği: Logger ve Service
#include <iostream>
#include <string>
#include <ctime>
class Logger {
public:
void log(const std::string& message) const {
time_t now = time(nullptr);
std::cout << "[LOG " << now << "] " << message << std::endl;
}
};
class DatabaseConnection {
private:
std::string connectionString;
bool connected = false;
public:
DatabaseConnection(const std::string& cs) : connectionString(cs) {}
bool connect() {
connected = true;
return true;
}
bool isConnected() const { return connected; }
std::string getInfo() const { return connectionString; }
};
class UserService {
private:
std::string serviceName;
// Logger veya DatabaseConnection TUTULMUYOR!
public:
UserService(const std::string& name) : serviceName(name) {}
// Logger'i parametre olarak aliyoruz - Dependency!
void createUser(const std::string& username, Logger& logger) {
logger.log(serviceName + ": Kullanici olusturuluyor - " + username);
// ... kullanici olusturma islemi ...
logger.log(serviceName + ": Kullanici olusturuldu - " + username);
}
// DatabaseConnection'i parametre olarak aliyoruz - Dependency!
bool checkHealth(const DatabaseConnection& db) {
if (db.isConnected()) {
std::cout << serviceName << ": DB baglantisi aktif - "
<< db.getInfo() << std::endl;
return true;
}
return false;
}
};
int main() {
Logger logger;
DatabaseConnection db("localhost:5432/mydb");
db.connect();
UserService service("UserService");
// Dependency: logger ve db sadece fonksiyon cagrisi sirasinda kullaniliyor
service.createUser("ali123", logger);
service.checkHealth(db);
return 0;
}UserService sınıfının içinde ne Logger ne de DatabaseConnection üye değişken olarak tutuluyor. Sadece fonksiyon çağrısı sırasında parametre olarak geçiriliyor ve işlem bitince ilişki sona eriyor. Bu dependency'dir.
Bir Fonksiyonda Lokal Kullanım da Dependency
class EmailSender {
public:
void send(const std::string& to, const std::string& body) const {
std::cout << "Email gonderildi -> " << to << ": " << body << std::endl;
}
};
class OrderService {
public:
void placeOrder(const std::string& item) {
std::cout << "Siparis verildi: " << item << std::endl;
// Lokal olarak olusturuluyor - Dependency!
EmailSender sender;
sender.send("musteri@email.com", "Siparisiz alindi: " + item);
// sender burada yok ediliyor, kalici bir bag yok
}
};EmailSender nesnesi fonksiyon içinde oluşturuluyor, kullanılıyor ve fonksiyon bitince yok ediliyor. OrderService sınıfı EmailSender'a bağımlı ama onu sahiplenmiyor. Bu da bir dependency ilişkisi.
UML Notasyonu — Görsel Olarak İfade Etmek
UML (Unified Modeling Language) diyagramlarında bu ilişkilerin her birinin kendine özgü bir sembolü var. Bunları bilmek ekip içi iletişimde çok faydalı.
Composition: [Whole] ◆────── [Part]
(dolu elmas, whole tarafinda)
Aggregation: [Whole] ◇────── [Part]
(bos elmas, whole tarafinda)
Association: [ClassA] ────── [ClassB]
(duz cizgi, ok ucu opsiyonel)
Dependency: [ClassA] ┄ ┄ ┄> [ClassB]
(kesik cizgi, ok ucuyla)Elmas sembolü her zaman bütün (whole) tarafında bulunur. Dolu elmas (◆) composition, boş elmas (◇) aggregation.
Kısa bir hatırlatma tablosu:
| Sembol | İlişki | Hatırlatıcı |
|---|---|---|
| ◆ | Composition | Dolu = güçlü bağ |
| ◇ | Aggregation | Boş = zayıf bağ |
| → | Association | Ok = tanışıklık |
| ⇢ | Dependency | Kesik ok = geçici |
Karar Tablosu: Hangisini Ne Zaman Kullanmalı?
Tasarım yaparken hangi ilişki türünü kullanman gerektiğine karar vermek zor olabilir. İşte sana yardımcı olacak bir karar ağacı:
Adım 1: Nesne Başka Bir Nesneyi Kullanıyor mu?
Hayır → İlişki yok
Evet → Adım 2'ye git
Adım 2: Kalıcı Bir Referans Tutuyor mu?
Hayır (sadece fonksiyon parametresi veya lokal değişken) → Dependency
Evet (üye değişken olarak) → Adım 3'e git
Adım 3: Sahiplik Var mı?
Hayır (iki taraf eşit, sadece birbirini tanıyor) → Association
Evet (bir taraf "bütün", diğeri "parça") → Adım 4'e git
Adım 4: Parçanın Yaşam Döngüsü Bütüne Bağlı mı?
Hayır (parça bütünsüz yaşayabilir) → Aggregation
Evet (parça bütünsüz anlamsız) → Composition
Özet Tablo
| Soru | Composition | Aggregation | Association | Dependency |
|---|---|---|---|---|
| Üye değişken? | ✅ | ✅ | ✅ | ❌ |
| Sahiplik? | ✅ Güçlü | ✅ Zayıf | ❌ | ❌ |
| Yaşam döngüsü bağlı? | ✅ | ❌ | ❌ | ❌ |
| Parça paylaşılabilir? | ❌ | ✅ | ✅ | N/A |
| C++ implementasyonu | Değer, unique_ptr | Pointer, shared_ptr | Pointer, reference | Parametre, lokal |
Gerçek Dünya Projesi: Okul Sistemi 🏫
Tüm ilişki türlerini bir arada görelim. Bir okul yönetim sistemi tasarlayacağız:
School ◆ Classroom → Composition (Sınıf, okulun parçası. Okul kapanınca sınıf yok.)
Classroom ◇ Student → Aggregation (Öğrenci sınıfa atanır ama bağımsız yaşar.)
Student → Teacher → Association (Öğrenci ve öğretmen birbirini tanır, eşit ilişki.)
Teacher ⇢ Printer → Dependency (Öğretmen yazıcıyı sadece sınav kağıdı basarken kullanır.)
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// Forward declarations
class Student;
class Teacher;
// ============================================
// Printer - Dependency ornek nesnesi
// ============================================
class Printer {
private:
std::string location;
public:
Printer(const std::string& loc) : location(loc) {}
void print(const std::string& document) const {
std::cout << "[Yazici - " << location << "] Yazdiriliyor: "
<< document << std::endl;
}
};
// ============================================
// Student - Bagimsiz varlik
// ============================================
class Student {
private:
std::string name;
int grade;
std::vector<Teacher*> teachers; // Association
public:
Student(const std::string& n, int g) : name(n), grade(g) {}
std::string getName() const { return name; }
int getGrade() const { return grade; }
void addTeacher(Teacher* t) { teachers.push_back(t); }
void showInfo() const {
std::cout << " Ogrenci: " << name
<< " (Not ort: " << grade << ")" << std::endl;
}
};
// ============================================
// Teacher - Bagimsiz varlik
// ============================================
class Teacher {
private:
std::string name;
std::string subject;
std::vector<Student*> students; // Association
public:
Teacher(const std::string& n, const std::string& s)
: name(n), subject(s) {}
std::string getName() const { return name; }
std::string getSubject() const { return subject; }
void addStudent(Student* s) { students.push_back(s); }
// Dependency: Printer'i sadece bu fonksiyonda kullaniyor
void printExam(const Printer& printer) const {
printer.print(subject + " Sinavi - Hazirlayan: " + name);
}
void showInfo() const {
std::cout << " Ogretmen: " << name
<< " (" << subject << ")" << std::endl;
}
};
// ============================================
// Classroom - Okulun parcasi (Composition ile olusturulacak)
// ============================================
class Classroom {
private:
std::string roomNumber;
int capacity;
std::vector<Student*> students; // Aggregation
public:
Classroom(const std::string& room, int cap)
: roomNumber(room), capacity(cap) {
std::cout << " Sinif olusturuldu: " << roomNumber << std::endl;
}
~Classroom() {
std::cout << " Sinif yok edildi: " << roomNumber << std::endl;
// Ogrencileri SILMIYORUZ - Aggregation!
}
void enrollStudent(Student* s) {
if (static_cast<int>(students.size()) < capacity) {
students.push_back(s);
std::cout << " " << s->getName() << " -> "
<< roomNumber << " sinifina eklendi." << std::endl;
} else {
std::cout << " " << roomNumber << " sinifi dolu!" << std::endl;
}
}
void showStudents() const {
std::cout << " Sinif " << roomNumber << " ("
<< students.size() << "/" << capacity << "):" << std::endl;
for (const auto* s : students) {
s->showInfo();
}
}
};
// ============================================
// School - En ust varlik, Classroom'larin sahibi
// ============================================
class School {
private:
std::string name;
std::vector<std::unique_ptr<Classroom>> classrooms; // Composition!
public:
School(const std::string& n) : name(n) {
std::cout << "Okul olusturuldu: " << name << std::endl;
}
~School() {
std::cout << "Okul yok ediliyor: " << name << std::endl;
// unique_ptr sayesinde Classroom'lar otomatik silinecek
}
Classroom* addClassroom(const std::string& room, int capacity) {
classrooms.push_back(std::make_unique<Classroom>(room, capacity));
return classrooms.back().get();
}
void showAllClassrooms() const {
std::cout << name << " - Tum siniflar:" << std::endl;
for (const auto& c : classrooms) {
c->showStudents();
}
}
};
// Association kurma yardimcisi
void assignTeacherToStudent(Teacher& t, Student& s) {
t.addStudent(&s);
s.addTeacher(&t);
}
int main() {
// Bagimsiz varliklar
Student ali("Ali", 85);
Student ayse("Ayse", 92);
Student can("Can", 78);
Teacher matematik("Hasan Hoca", "Matematik");
Teacher fizik("Sevgi Hoca", "Fizik");
Printer officePrinter("Ogretmenler Odasi");
{
// Okul ve siniflari (Composition)
School school("Ataturk Lisesi");
Classroom* sinif10A = school.addClassroom("10-A", 30);
Classroom* sinif10B = school.addClassroom("10-B", 25);
// Ogrencileri siniflara ekle (Aggregation)
sinif10A->enrollStudent(&ali);
sinif10A->enrollStudent(&ayse);
sinif10B->enrollStudent(&can);
// Ogretmen-ogrenci iliskisi (Association)
assignTeacherToStudent(matematik, ali);
assignTeacherToStudent(matematik, ayse);
assignTeacherToStudent(fizik, ali);
assignTeacherToStudent(fizik, can);
// Ogretmen yazici kullaniyor (Dependency)
matematik.printExam(officePrinter);
fizik.printExam(officePrinter);
std::cout << std::endl;
school.showAllClassrooms();
std::cout << "\n--- Okul kapaniyor ---" << std::endl;
}
// School yok edildi → Classroom'lar da yok edildi (Composition)
// Ama Student'lar ve Teacher'lar hala yasiyor!
std::cout << "\n--- Okul kapandiktan sonra ---" << std::endl;
ali.showInfo(); // Ali hala burada!
matematik.showInfo(); // Hasan Hoca hala burada!
return 0;
}Bu örnekte dört ilişki türünü de bir arada görüyorsun:
School ◆ Classroom:
unique_ptrile composition. Okul yok edilince sınıflar da gidiyor.Classroom ◇ Student: Raw pointer ile aggregation. Sınıf yok edilince öğrenciler hayatta.
Teacher ↔ Student: Çift yönlü association. İkisi birbirini tanıyor, kimse kimseyi sahiplenmiyor.
Teacher ⇢ Printer: Dependency. Öğretmen yazıcıyı sadece
printExamfonksiyonunda kullanıyor.
Sık Yapılan Hatalar
Hata 1: Her Şeyi Composition Yapmak
Yeni başlayanlar her ilişkiyi composition olarak modeller. Sonuç: esnek olmayan, test edilmesi zor bir kod. Önce kendine sor: "Bu parça gerçekten bütünsüz anlamsız mı?"
Hata 2: Aggregation'da Sahipliği Belirsiz Bırakmak
Raw pointer kullanıyorsan ve kimin delete edeceği belli değilse, er ya da geç memory leak veya crash yaşarsın. Sahiplik belirsizse shared_ptr kullan.
Hata 3: Dependency Yerine Association Kullanmak
Bir nesneyi sadece tek bir fonksiyonda kullanıyorsan, onu üye değişken yapmana gerek yok. Gereksiz yere üye değişken yapmak coupling'i (bağımlılığı) artırır.
Hata 4: Circular Dependency (Dairesel Bağımlılık)
A, B'ye bağımlı; B, A'ya bağımlı. Bu durum özellikle association'da sıkça ortaya çıkar. Forward declaration ve dikkatli tasarımla çözülür.
💡 İpucu: Bir ilişki türü seçerken, her zaman en zayıf ilişkiyi tercih et. Dependency yetiyorsa association yapma, association yetiyorsa aggregation yapma. Bu prensip kodunu daha esnek, test edilebilir ve bakımı kolay hale getirir. Buna "Principle of Least Coupling" denir.
SOLID ile Bağlantı
Nesne ilişkileri, SOLID prensipleriyle doğrudan bağlantılıdır:
Single Responsibility (SRP): Her sınıf kendi işini yapar. İlişki türleri, sorumlulukları doğru dağıtmana yardımcı olur.
Dependency Inversion (DIP): Yüksek seviyeli modüller, düşük seviyeli modüllere doğrudan bağımlı olmamalı. Association ve dependency'de soyutlama (abstraction) kullanarak bağımlılığı tersine çevirebilirsin.
Open/Closed (OCP): Aggregation ve association kullanarak sistemi genişletmeye açık, değişikliğe kapalı yapabilirsin.
Bu prensipleri daha sonraki derslerde detaylıca inceleyeceğiz. Şimdilik nesne ilişkilerinin bu prensiplerin temelini oluşturduğunu bilmek yeterli.
İleri Seviye: İlişki Türlerini Birlikte Düşünmek
Gerçek projelerde ilişkiler nadiren tek başına var olur. Genellikle bir sınıf birden fazla ilişki türüne sahiptir:
class Game {
private:
Board board; // Composition (oyun tahtasi)
std::vector<std::shared_ptr<Player>> players; // Aggregation (oyuncular)
Referee* referee; // Association (hakem)
public:
void saveGame(FileWriter& writer) { // Dependency (dosya yazici)
// ...
}
};Bu tamamen normal ve sağlıklı bir tasarım. Önemli olan her ilişkinin bilinçli bir seçim olması. "Neden composition?" sorusuna cevap verebilmelisin.
Özet
Composition (◆): Güçlü sahiplik. Parça, bütünün yaşam döngüsüne bağlı. Değer veya
unique_ptrile implementasyon. Araba-motor ilişkisi.Aggregation (◇): Zayıf sahiplik. Parça bağımsız var olabilir, paylaşılabilir. Pointer veya
shared_ptrile implementasyon. Üniversite-öğrenci ilişkisi.Association (→): Bağımsız ilişki. İki taraf eşit, sadece birbirini tanır. Sahiplik yok. Doktor-hasta ilişkisi.
Dependency (⇢): En zayıf ilişki. Geçici kullanım — fonksiyon parametresi veya lokal değişken. Taksici-yolcu ilişkisi.
Tasarımda her zaman en zayıf ilişki türünü tercih et. Gereksiz bağımlılık, bakımı ve test edilebilirliği zorlaştırır.
Karar verirken sor: "Parça bütünsüz yaşayabilir mi?", "Kalıcı referans gerekli mi?", "Sahiplik var mı?"
AI Asistan
Sorularını yanıtlamaya hazır