My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more

Post hidden from Hashnode

Posts can be hidden from Hashnode network for various reasons. Contact the moderators for more details.

Örneklerle SOLID Prensipleri

Her programcının çok iyi bilmesi gereken SOLID prensiplerini kısa ve anlaşılabilir örneklerle öğrenin...

Ozan Hazer's photo
Ozan Hazer
·Nov 18, 2021·

4 min read

Her programcının bilmesi gereken SOLID, sektör tarafından kabul edilen nesne yönelimli tasarım prensiplerinin ilk beşini temsil eder. Bu temel prensipler uygulandığında zamanla büyüyen ve bu nedenle karmaşıklaşan yazılım projeleri uzun vadede yönetilebilir ve geliştirilebilir.

Kabul edelim, hangi prensibi uygularsanız uygulayın projeler zamanla çorbaya dönerler. Bu prensipleri uyguladığınızda hayat bayram olmayacak belki fakat uygulamazsanız pişman olacağınızı garanti ederim...

SOLID prensipleri code smell, refactoring, agile development gibi diğer yazılım terminolojilerinde de karşınıza çıkacak...

SOLID Prensipleri Nelerdir?

  • S - Single-Responsibility Principle (SRP)
  • O - Open/Closed Principle
  • L - Liskov Substitiution Principle
  • I - Interface Segregation Principle
  • D - Dependency Inversion Principle

SOLID prensiplerinin yararı var olan sınıflarınıza yeni özellikler eklerken, proje büyürken ortaya çıkar. Sınıfları yeni tasarlarken ihtiyaç hissetmeyebilirsiniz, çok takılmayın. İyi programcılar sınıflara her yeni özellik eklerken bu prensipler doğrultusunda küçük refactoring'ler yaparak ilerlerler.

Single-Responsibility Principle

Bir nesnenin sadece bir görevi olmalıdır ve kendi işi dışında başka bir iş yapmamalıdır.

Hatalı yaklaşım:

/**
 * Kişinin bilgilerini barındırmanın dışında eposta da gönderiyor.
 */
class Person
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}

    public function sendEmail(string $message) {
        mail($this->email, 'Deneme', $message);
    }
}

$person = new Person('Ozan Hazer', 'ozan@bilkent.edu.tr');
$person->sendEmail('Deneme mesajı');

Doğru yaklaşım, eposta gönderimi için farklı bir sınıf kullanmak olmalı:

class Person
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

class Mailer
{
    public function __construct(private Person $person) {}
    public function send(string $message) {
        mail($this->person->email, 'Deneme', $message);
    }
}

$person = new Person('Ozan Hazer', 'ozan@bilkent.edu.tr');
(new Mailer($person))->sendEmail('Deneme mesajı');

Faydaları

  • İlerleyen zamanda yazılıma bir sürü özellik eklendiğini hayal edin. İki bin satır koda sahip içerisinde onlarca metot bulunan tek bir sınıfı mı yönetmek daha kolay, yoksa ne iş yaptığı belli olan küçük küçük sınıfları mı?
  • Kullanıcılara SMS gönderme özelliği istenirse? Push notification? Slack'ten mesaj gönderme özelliği? SRP'ye uymazsanız her bir mesaj kanalı içi ayrı bir metotlar, konfigürasyonlar eklemeniz gerekecek.

Notlar

  • SRP prensibini abartırsanız her birinde bir-iki adet metot bulunan bir sürü sınıfınız olur ve bu da işleri kolaylaştıracağına zorlaştırır, nur topu gibi şahane bir spagetti kodunuz olabilir.
  • SRP sadece sınıflar için değil yazılımın her yerinde kullanmanız gereken bir temel mühendislik prensibidir. Örneğin business logic ile presentation logic'i ayırma ihtiyacı da bu prensibe dayanır.
  • Herhangi bir sınıf, metot ya da fonksiyonun çok fazla satır koda sahipse SRP prensibinin bozulduğuna dair bir işaret olarak değerlendirmelisiniz.

Open/Closed Principle

Bu prensibi Open for extension, closed for modification olarak aklınızda tutabilirsiniz. Bir sınıfa yeni bir kabiliyet eklerken var olan koda dokunmadan, sınıfı genişletiyor olmalısınız.

Örnek olarak birden fazla şeklin alanlarının toplamını hesaplayan bir sınıf düşünebiliriz.

class Rectangle
{
    public function __construct(
        public int $width,
        public int $height,
    ) {}
}

class Square
{
    public function __construct(
        public int $length,
    ) {}
}

class AreaCalculator
{
    public function __construct(
        protected array $shapes
    ) {}

    public function sum()
    {
        $sum = 0;
        foreach ($this->shapes as $shape) {
            if ($shape instanceof Square) {
                $sum += pow($shape->length, 2);
            } elseif ($shape instanceof Rectangle) {
                $sum += $shape->width * $shape->height;
            }
        }
        return $sum;
    }
}

$areas = [
    new Rectangle(100, 50),
    new Square(25),
];
$areaCalculator = new AreaCalculator($areas);
$areaCalculator->sum();

Diyelim projeye sonradan Circle diye bir şekil daha eklenmesi isteği geldi, bu durumda AreaCalculator sınıfındaki sum metoduna bir if daha eklememiz gerekecek.

Şu aşamada çok da büyük bir sorun değil diyebilirsiniz, peki ya bu şekilleri kullanan onlarca daha sınıf olsaydı? Hepsine tek tek gidip güncellemek gerekecekti, işte open/closed prensibinin çıkış noktası da tam olarak bu...

Bu örnek için doğru yaklaşım bir şekillerin hepsini kapsayan bir interface tanımlamak olacak:

interface Shape
{
    public function area();
}

class Rectangle implements Shape
{
    public function __construct(
        public int $width,
        public int $height,
    ) {}

    public function area() {
        return $this->width * $this->height;
    }
}

class Square implements Shape
{
    public function __construct(
        public int $length,
    ) {}

    public function area() {
        return pow($this->length, 2);
    }
}

class AreaCalculator
{
    public function __construct(
        protected array $shapes
    ) {}

    public function sum() {
        $sum = 0;
        foreach ($this->shapes as $shape) {
            $sum += $shape->area();
        }
        return $sum;
    }
}

Notlar

  • Open/closed prensibini ihlal eden bir kod büyük ihtimalle SRP ve Dependency Inversion prensiplerini de ihlal eder.
  • Bu örnekte başka OOP sorunları da var, örneğin abstraction. AreaCalculator::sum() metodu tam olarak kendini soyutlayamamış, gönderilen şeklin ne olduğunu bilmek ve hatta her şeklin alanının nasıl hesaplandığını da bilmek zorunda (if ile instanceof kontrollerinin olduğu satırlar).

Liskov Substitution Principle

Bir sınıfı extend ettiğinizde, oluşturduğunuz alt sınıf (subclass), üst sınıfın yerine kullanılabiliyor olmalıdır.

Bu prensiple artık özdeşleşen şu resim konuyu özetliyor:

image.png

Kod görmek isteyenler için bir örneğe bakalım.

interface Animal {}
class Cat implements Animal {}
class MaineCoon extends Cat {}

Bu arada bilmeyenler için maine coon efsane güzel bir kedir cinsidir

image.png

Interface Segregation Principle

Bir interface'i implement eden sınıflar, o interface'de tanımlanan tüm tanımları kullanmalıdır.

...örnekler...

Dependency Inversion Principle

Üst seviye (High-Level) sınıflar alt seviye (Low-Level) sınıflara bağlı olmamalıdır, abstraction (soyutlamaya) bağlı olmalıdır.

Anlamadınız değil mi? Ben de :)

...örnekler...

Kaynaklar