Takip et

Yazılım Geliştirmede SOLID Prensipleri: Sağlam ve Esnek Kodlama İçin Rehber

Yazılım dünyasında, projeler büyüdükçe kod tabanlarının karmaşıklığı da artar. Bu karmaşıklıkla baş etmenin, kodunuzu esnek, anlaşılır, sürdürülebilir ve test edilebilir kılmanın anahtarı, kanıtlanmış tasarım prensiplerini uygulamaktan geçer. İşte SOLID prensipleri tam olarak bu noktada devreye girer.

Robert C. Martin (nam-ı diğer “Uncle Bob”) tarafından ortaya konan SOLID, Nesne Yönelimli Programlama (OOP) için beş altın kuralı temsil eden bir akronimdir. Bu makalede, her bir prensibi detaylıca inceleyecek, neden önemli olduklarını anlayacak ve PHP dilinden örneklerle nasıl uygulandıklarını göreceğiz.

SOLID, beş temel nesne yönelimli tasarım prensibinin baş harflerinden oluşur:

  • S – Single Responsibility Principle (Tek Sorumluluk Prensibi)

  • O – Open-Closed Principle (Açık-Kapalı Prensibi)

  • L – Liskov Substitution Principle (Liskov Yerine Geçme Prensibi)

  • I – Interface Segregation Principle (Arayüz Ayrımı Prensibi)

  • D – Dependency Inversion Principle (Bağımlılıkların Ters Çevrilmesi Prensibi)

Bu prensipler yalnızca teorik kavramlar değil; uygulandıklarında yazılımcıya daha temiz, okunabilir, test edilebilir ve esnek yazılım geliştirme imkânı sağlar.

SOLID Neden Önemlidir?

SOLID prensipleri, yazılım geliştirmede karşılaşılan şu dört temel soruna çözüm sunar:

  • Rijitlik (Rigidity): Bir değişiklik yapmanın, bir dizi başka ilişkili değişikliği zorunlu kılması.

  • Kırılganlık (Fragility): Yapılan bir değişikliğin, görünüşte ilgisiz başka yerlerde beklenmedik hatalara neden olması.

  • Hareketsizlik (Immobility): Kod parçalarını başka projelerde yeniden kullanmanın zor olması çünkü sistem içinde sıkı sıkıya bağlıdırlar.

  • Ağırlık (Viscosity): Doğru olanı yapmanın (prensiplere uygun), ‘hızlı ve kirli’ çözümden daha zor ve zahmetli hale gelmesi.

SOLID, bu sorunları azaltarak daha Agile (çevik) ve Adaptive (uyum sağlayabilir) yazılım geliştirmenin önünü açar.


1. S – Single-Responsibility Principle (Tek Sorumluluk Prensibi)

Tanım: “Bir sınıfın değişmesi için yalnızca bir sebebi olmalıdır.” Başka bir deyişle, bir sınıf yalnızca bir işi yapmalı ve o işi çok iyi yapmalıdır.

Sorun: Aşağıdaki AreaCalculator sınıfı, hem şekillerin alanlarını toplama hem de bu sonucu HTML formatında çıktılama sorumluluğunu üstlenmiştir. Bu, iki farklı değişim sebebi doğurur: hesaplama mantığı değişebilir veya çıktı formatı (JSON, XML vs. istenebilir) değişebilir.

// SRP'yi İhlal Eden Sınıf
class AreaCalculator {
    protected $shapes;

    public function __construct($shapes = []) {
        $this->shapes = $shapes;
    }

    public function sum() {
        // ... Alanları hesapla ...
    }

    public function output() { // Çıktı sorumluluğu burada -> Problem!
        return implode('', [
            '',
            'Sum of the areas of provided shapes: ',
            $this->sum(),
            '',
        ]);
    }
}

Çözüm: Sorumlulukları ayır. Hesaplama ve çıktılama işlemleri için iki ayrı sınıf oluştur.

// Sadece hesaplama sorumluluğu
class AreaCalculator {
    protected $shapes;

    public function __construct($shapes = []) {
        $this->shapes = $shapes;
    }

    public function sum() {
        foreach ($this->shapes as $shape) {
            $area[] = $shape->area(); // Her şekil kendi alanını hesaplar
        }
        return array_sum($area);
    }
}

// Sadece çıktılama sorumluluğu
class SumCalculatorOutputter {
    protected $calculator;

    public function __construct(AreaCalculator $calculator) {
        $this->calculator = $calculator;
    }

    public function HTML() {
        return implode('', [
            '',
            'Sum of the areas of provided shapes: ',
            $this->calculator->sum(),
            '',
        ]);
    }

    public function JSON() {
        return json_encode(['sum' => $this->calculator->sum()]);
    }
}

// Kullanım
$shapes = [new Circle(2), new Square(5)];
$areaCalculator = new AreaCalculator($shapes);
$outputter = new SumCalculatorOutputter($areaCalculator);

echo $outputter->HTML();
echo $outputter->JSON();

Faydası: Kod daha düzenli hale gelir. Hesaplama mantığı değişirse yalnızca AreaCalculator, çıktı formatı değişirse yalnızca SumCalculatorOutputter sınıfı değiştirilir. Değişimler izole edilmiştir.


2. O – Open-Closed Principle (Açık/Kapalı Prensibi)

Tanım: “Yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) genişletmeye açık, ancak değişikliğe kapalı olmalıdır.” Mevcut, çalışan kodu değiştirmeden yeni özellikler ekleyebilmeliyiz.

Sorun: Aşağıdaki sum metoduna her yeni şekil eklendiğinde (üçgen, beşgen), mevcut koda elseif bloğu eklemek zorunda kalırız. Bu, prensibi ihlal eder çünkü temel sınıfı değiştirmiş oluruz.

class AreaCalculator {
    // ...
    public function sum() {
        $area = [];
        foreach ($this->shapes as $shape) {
            if (is_a($shape, 'Square')) {
                $area[] = pow($shape->length, 2);
            } elseif (is_a($shape, 'Circle')) { // Yeni bir şekil? Yeni bir 'elseif'!
                $area[] = pi() * pow($shape->radius, 2);
            }
            // Yeni bir şekil için burayı DEĞİŞTİRMEK zorundayız.
        }
        return array_sum($area);
    }
}

Çözüm: Soyutlamalara (interface’ler veya abstract sınıflar) bağlı çalış. Yeni bir özellik eklemek için mevcut kodu değiştirmek yerine, soyutlamayı uygulayan yeni bir sınıf yaratırız.

// Bir Soyutlama (Abstraction) - Değişime kapalı
interface ShapeInterface {
    public function area();
}

// Somut Implementasyonlar - Genişlemeye açık
class Square implements ShapeInterface {
    public function area() { return pow($this->length, 2); }
}

class Circle implements ShapeInterface {
    public function area() { return pi() * pow($this->radius, 2); }
}

// Yeni bir şekil eklemek için mevcut koda DOKUNMAYIZ.
class Triangle implements ShapeInterface {
    public function area() { /* ...Üçgen alanı hesabı... */ }
}

// AreaCalculator sınıfı artık değişime kapalıdır.
class AreaCalculator {
    // ...
    public function sum() {
        foreach ($this->shapes as $shape) {
            if ($shape instanceof ShapeInterface) {
                $area[] = $shape->area(); // Nasıl hesaplandığını bilmez, sadece 'area' metodunu çağırır.
                continue;
            }
            throw new Exception("Geçersiz şekil");
        }
        return array_sum($area);
    }
}

Faydası: Sistem, mevcut kodu bozma riski olmadan ve minimum çabayla yeni özelliklerle genişletilebilir.


3. L – Liskov Substitution Principle (Liskov’un Yerine Geçme Prensibi)

Tanım: “Bir programdaki nesneler, kendi alt türlerinin örnekleriyle değiştirilebilmelidir ve bu değişim programın doğruluğunu bozmamalıdır.” Kalıtım alan bir sınıf, temel sınıfın tüm davranışlarını korumalı ve beklenmeyen sürprizler yapmamalıdır.

Sorun: AreaCalculator‘dan kalıtım alan VolumeCalculator sınıfı, sum metodunun dönüş tipini (integer/float yerine dizi) değiştiriyor. Bu, temel sınıfın kullanıldığı her yerde (SumCalculatorOutputter) onun yerine kullanılamayacağı anlamına gelir ve hataya sebep olur.

class VolumeCalculator extends AreaCalculator {
    public function sum() {
        // Hacimleri hesapla...
        return [$summedData]; // Beklenen bir sayı yerine dizi döndürür -> Problem!
    }
}

$volumes = new VolumeCalculator($solidShapes);
$output = new SumCalculatorOutputter($volumes);
echo $output->HTML(); // HATA: Diziyi string gibi yazdırmaya çalışır.

Çözüm: Alt sınıflar, üst sınıfların davranışlarını ve imzalarını (dönüş tipleri gibi) bozmamalıdır. Üst sınıfın sözleşmesine sadık kalmalıdırlar.

class VolumeCalculator extends AreaCalculator {
    public function sum() {
        // Hacimleri hesapla...
        return $summedData; // Üst sınıf gibi bir sayı (int/float) döndürür.
    }
}

Faydası: Kod içgüdüsel olarak doğru çalışır. Üst sınıf referansı tutan bir nesneye alt sınıf atadığınızda nasıl davranacağından emin olabilirsiniz. Bu, polimorfizmin güvenli bir şekilde kullanılmasını sağlar.


4. I – Interface Segregation Principle (Arayüz Ayrıştırma Prensibi)

Tanım: “Hiçbir istemci, kullanmadığı metodlara bağımlı olmaya zorlanmamalıdır.” Büyük, monolitik arayüzler yerine, istemciye özel, küçük ve odaklanmış arayüzler tercih edilmelidir.

Sorun: Tüm şekilleri tek bir ShapeInterface içinde toplamak, Square gibi 2B şekilleri, ihtiyaç duymadıkları volume metodunu implemente etmeye zorlar.

// Monolitik, "şişman" arayüz -> Problem!
interface ShapeInterface {
    public function area();
    public function volume(); // 2B şekillerin bunu implemente etmesi anlamsızdır.
}

class Square implements ShapeInterface {
    public function area() { /* ... */ }
    public function volume() { /* ... */ } // Boş bir metod? Veya istisna fırlatmak? -> Kötü tasarım.
}

Çözüm: Arayüzleri, belirli istemci ihtiyaçlarına göre daha küçük parçalara böl.

// Ayrıştırılmış, spesifik arayüzler
interface TwoDimensionalShapeInterface {
    public function area();
}

interface ThreeDimensionalShapeInterface {
    public function volume();
}

// İstemci sınıflar sadece ihtiyaç duydukları arayüzü implemente eder.
class Square implements TwoDimensionalShapeInterface {
    public function area() { /* ... */ } // Sadece alan, sorumluluk net.
}

class Cuboid implements TwoDimensionalShapeInterface, ThreeDimensionalShapeInterface {
    public function area() { /* Yüzey alanı */ } // İkisini de kullanır.
    public function volume() { /* Hacim */ }
}

Faydası: Sistem daha az “bağımlılık yükü” taşır. Sınıflar daha az metoda sahip, daha basit arayüzlerle çalışır, bu da kodun anlaşılırlığını ve sürdürülebilirliğini artırır.


5. D – Dependency Inversion Principle (Bağımlılıkların Tersine Çevrilmesi Prensibi)

Tanım: “Üst seviye modüller, alt seviye modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır. Soyutlamalar ayrıntılara bağlı olmamalı, ayrıntılar soyutlamalara bağlı olmalıdır.”

Sorun: Üst seviye bir modül olan PasswordReminder, alt seviye bir modül olan MySQLConnection sınıfına doğrudan ve somut bir şekilde bağımlıdır. Veritabanı değiştiğinde (PostgreSQL’e geçiş), üst seviye modülü değiştirmek zorunda kalırız.

class MySQLConnection { // Alt seviye modül (Detay)
    public function connect() { /* ... */ }
}

class PasswordReminder { // Üst seviye modül (İş mantığı)
    private $dbConnection;

    public function __construct(MySQLConnection $dbConnection) { // Somut sınıfa bağımlı -> Problem!
        $this->dbConnection = $dbConnection; // PostgreSQL'e geçemeyiz.
    }
}

Çözüm: Üst seviye modül, bir soyutlama (arayüz) aracılığıyla çalışmalıdır. Alt seviye modül ise bu soyutlamayı implemente etmelidir. Böylece bağımlılık yönü “tersine çevrilmiş” olur.

// Soyutlama
interface DBConnectionInterface {
    public function connect();
}

// Alt seviye modül soyutlamaya bağlı (Detay -> Soyutlama)
class MySQLConnection implements DBConnectionInterface {
    public function connect() { return "MySQL bağlantısı"; }
}
class PostgreSQLConnection implements DBConnectionInterface {
    public function connect() { return "PostgreSQL bağlantısı"; }
}

// Üst seviye modül soyutlamaya bağlı (İş Mantığı -> Soyutlama)
class PasswordReminder {
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection) { // Artık somut sınıf değil, arayüz!
        $this->dbConnection = $dbConnection; // MySQL, PostgreSQL, MongoDB... Hepsi uyar.
    }
}

// Kullanım - Bağımlılık "Dışarıdan Enjekte Ediliyor" (Dependency Injection)
$mysql = new MySQLConnection();
$reminder = new PasswordReminder($mysql); // MySQL ile

$postgresql = new PostgreSQLConnection();
$reminder = new PasswordReminder($postgresql); // PostgreSQL ile -> PasswordReminder'da DEĞİŞİKLİK GEREKMEZ!

Faydası: Modüller arası gevşek bağlılık (loose coupling) sağlanır. Sistem çok daha esnek ve test edilebilir hale gelir. Ünite testlerde gerçek bir veritabanı yerine DBConnectionInterface‘i implemente eden bir sahte (mock) nesne kullanılabilir.


Sonuç

SOLID prensipleri, karmaşık yazılım sistemlerini yönetilebilir parçalara ayırmanın ve nesne yönelimli tasarımın kalitesini artırmanın sistematik bir yoludur. Bu prensipler birer “kural”dan ziyade, deneyimle olgunlaşan bir “düşünce tarzıdır”. İlk başta uygulamak zor gelse de, zamanla kodunuzda ne zaman bir koku hissettiğinizi ve hangi prensibi uygulayarak bu kokuyu giderebileceğinizi içselleştirirsiniz.

SOLID prensipleri, yazılım geliştiricilere yalnızca teorik bir yol haritası sunmakla kalmaz, aynı zamanda günlük hayatta kullanılan projelerde temiz, esnek ve sürdürülebilir kod yazmanın temelini oluşturur.

Bu prensipler sayesinde:

  • Kodunuz daha kolay okunur,

  • Yeni özellik eklemek mevcut sistemi bozmadan yapılabilir,

  • Test edilebilirlik ve bakım maliyeti azalır,

  • Uzun vadeli projelerde kodun yaşam ömrü uzar.

Kısacası, SOLID prensipleri yazılım mühendisliğinin altın kurallarıdır. Onları öğrenmek, uygulamak ve içselleştirmek, yazılım geliştiriciyi bir üst seviyeye taşır.

Unutmayın, amaç katı bir şekilde kurallara uymak değil, daha temiz, anlaşılır ve dayanıklı yazılımlar üretebilmektir. SOLID, bu uzun ve keyifli yolculukta size rehberlik edecek en güvenilir arkadaşlarınızdan biri olacaktır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık