Gerçek Projelerde Design Patterns

Geliştiriciler bunları gerçek yazılım projelerinde nasıl kullanacaklarını anladığında Design Patterns gerçekten değerli hale gelir. Kalıpları tek tek öğrenmek önemlidir ancak profesyonel yazılım geliştirme, her kalıbın ne zaman kullanılacağını, kalıpların birlikte nasıl çalıştığını ve ne zaman kullanılmayacağını bilmeyi gerektirir.

Gerçek uygulamalarda Repository, Service Layer, DTO, Factory, Strategy, Adapter, Decorator, Observer, Command, Facade ve Dependency Injection gibi desenler sıklıkla bir arada görünür. Geliştiricilerin temiz, esnek, bakımı kolay ve test edilmesi daha kolay yazılımlar geliştirmelerine yardımcı olurlar.

Giriş

Bu OOP ve Design Patterns serisi boyunca birçok önemli model ve kavramı tartıştık. Object-Oriented Programming temellerini, kalıtım, kapsülleme, çok biçimlilik, soyutlama, arayüzler, statik yöntemler, ad alanları, otomatik yükleme, OOP en iyi uygulamaları ve birçok tasarım modelini ele aldık.

Ancak Design Patternsnı tek tek öğrenmek yalnızca ilk adımdır. Daha önemli adım, kod üzerinde aşırı mühendislik yapmadan bunların gerçek projelerde nasıl uygulanacağını anlamaktır.

Bu makale pratik kullanıma odaklanmaktadır. Design Patternsnin gerçek uygulamalarda nasıl göründüğünü, ortak iş akışlarını nasıl desteklediklerini ve geliştiricilerin çözdükleri soruna göre doğru modeli nasıl seçebileceklerini açıklıyor.

Design Patterns Gerçek Projelerde Neden Önemlidir?

Design Patterns önemlidir çünkü gerçek projeler zamanla değişir. Gereksinimler değişir, yeni özellikler eklenir, dış hizmetler değiştirilir, iş kuralları daha karmaşık hale gelir ve ekipler büyür.

İyi bir yapı olmadan bir projenin sürdürülmesi hızla zorlaşabilir. Denetleyiciler çok büyür, hizmetler dağınık hale gelir, database sorguları tekrarlanır, harici API kodu her yere yayılır ve küçük değişiklikler beklenmedik hatalara neden olur.

Design Patterns, kodu düzenlemenin kanıtlanmış yollarını sağlar. Geliştiricilerin sorumlulukları ayırmasına, tekrarları azaltmasına, soyutlamalara bağımlı olmasına ve kodun genişletilmesini kolaylaştırmasına yardımcı olurlar.

Design Patterns Sihir Değildir

Design Patterns sihirli çözümler değildir. Kodu otomatik olarak temizlemezler. Yanlış yerde kullanılan bir kalıp kodu daha karmaşık hale getirebilir.

Amaç mümkün olduğu kadar çok kalıp kullanmak değil. Amaç basit ve uygun yapıyla gerçek tasarım problemlerini çözmektir.

İyi bir geliştirici "Burada hangi modeli zorlayabilirim?" diye sormaz. İyi bir geliştirici şu soruyu sorar: "Hangi sorunum var ve hangi tasarım yaklaşımı kodu daha anlaşılır kılıyor?"

Design Patterns Tarafından Çözülen Yaygın Gerçek Proje Sorunları

Design Patterns faydalıdır çünkü gerçek projelerde tekrar tekrar ortaya çıkan yaygın yazılım sorunlarını çözerler.

Yaygın sorunlar şunları içerir:

  • Kontrolörler çok büyüyor.

  • İş mantığı birçok yerde kopyalanıyor.

  • Veritabanı sorguları hizmetler ve denetleyicilerle karıştırılıyor.

  • Harici API ayrıntıları kod tabanına yayılıyor.

  • Farklı davranışlar için çok fazla if ve switch ifadesi var.

  • Gizli bağımlılıklara sahip test edilmesi zor sınıflar.

  • Olaylara, işlere veya komutlara ihtiyaç duyan karmaşık iş akışları.

  • Çok fazla şey yapan büyük sınıflar.

Her tasarım modeli, doğru kullanıldığında bu sorunlardan bir veya daha fazlasının çözümüne yardımcı olabilir.

Gerçek Proje Örneği: Kullanıcı Kaydı

Kullanıcı kaydı hemen hemen her web uygulamasında ortak bir özelliktir. Basit bir kayıt özelliği, kullanıcı kaydı oluşturmakla başlayabilir. Daha sonra validation, şifre karma, profil oluşturma, hoş geldiniz e-postası, e-posta doğrulama, denetim günlüğü tutma, analiz ve yönetici bildirimini içerebilir.

Bu özellikte çeşitli desenler birlikte çalışabilir:

  • DTO Modeli: Doğrulanmış kayıt verilerini taşır.

  • Service Layer Modeli: Kayıt iş akışını düzenler.

  • Repository Pattern: Kullanıcı verilerine erişimi yönetir.

  • Dependency Injection: Hizmete bağımlılıklar sağlar.

  • Observer Pattern: Dinleyicilerin kayıttan sonra tepki vermelerine olanak tanır.

  • Command Pattern: Daha büyük sistemlerde kaydı bir komut olarak temsil edebilir.

Sonuç, daha temiz ve daha sürdürülebilir bir kayıt akışıdır.

PHP'da Kullanıcı Kaydı Örneği

readonly class RegisterUserDto
{
    public function __construct(
        public string $name,
        public string $email,
        public string $password
    ) {
    }
}

class UserRegistrationService
{
    public function __construct(
        private UserRepositoryInterface $users,
        private PasswordHasher $passwordHasher,
        private EventDispatcherInterface $events
    ) {
    }

    public function register(RegisterUserDto $dto): User
    {
        if ($this->users->findByEmail($dto->email)) {
            throw new RuntimeException('Email already exists.');
        }

        $user = $this->users->create([
            'name' => $dto->name,
            'email' => $dto->email,
            'password' => $this->passwordHasher->hash($dto->password),
        ]);

        $this->events->dispatch(new UserRegistered($user));

        return $user;
    }
}

Bu örnekte DTO, Service Layer, Repository, Dependency Injection ve Observer tarzı olay gönderimi kullanılmaktadır. Her parçanın net bir sorumluluğu var.

Gerçek Proje Örneği: E-Ticaret Ödeme

Ödeme daha karmaşık bir gerçek dünya özelliğidir. validation sepeti, indirim hesaplaması, nakliye maliyeti hesaplaması, ödeme işleme, sipariş oluşturma, envanter güncelleme, fatura oluşturma, onay e-postası ve analiz takibini içerebilir.

Çeşitli Design Patterns bu iş akışını destekleyebilir:

  • Service Layer: Ödeme sürecini koordine eder.

  • Strategy Pattern: Ödeme yöntemlerini, gönderim kurallarını veya indirim kurallarını yönetir.

  • Factory Pattern: Doğru ödeme veya gönderim stratejisini oluşturur.

  • Repository Pattern: Siparişleri saklar ve ürünleri alır.

  • Adapter Pattern: Harici ödeme ağ geçitlerine bağlanır.

  • Observer Pattern: Sipariş verildikten sonra yan etkileri tetikler.

  • Command Pattern: E-posta veya fatura oluşturma gibi görevleri yavaşlatır.

Bu kombinasyon, ödeme mantığının tek bir büyük denetleyici yöntemi haline gelmek yerine düzenli kalmasına olanak tanır.

Ödeme Hizmeti Örneği

class CheckoutService
{
    public function __construct(
        private CartValidator $cartValidator,
        private DiscountStrategy $discountStrategy,
        private PaymentGatewayInterface $paymentGateway,
        private OrderRepositoryInterface $orders,
        private InventoryService $inventory,
        private EventDispatcherInterface $events
    ) {
    }

    public function checkout(User $user, Cart $cart): Order
    {
        $this->cartValidator->validate($cart);

        $discount = $this->discountStrategy->calculate($cart);

        $order = $this->orders->createFromCart($user, $cart, $discount);

        $this->paymentGateway->charge($order);
        $this->inventory->decreaseStock($cart);

        $this->events->dispatch(new OrderPlaced($order));

        return $order;
    }
}

Bu hizmet ana iş akışını koordine eder. Ödeme, indirim, envanter ve etkinlik yönetimi kendi bileşenlerine ayrılmıştır.

Gerçek Proje Örneği: Ödeme Ağ Geçidi Entegrasyonu

Ödeme ağ geçidi entegrasyonu, Design Patternsnin çok faydalı olduğu ortak bir alandır. Stripe, PayPal, banka havalesi veya yerel ödeme sistemleri gibi farklı sağlayıcılar farklı APIs ve yanıt formatlarına sahip olabilir.

Yararlı desenler şunları içerir:

  • Adapter Pattern: Sağlayıcıya özel APIs'yu ortak bir dahili arayüze dönüştürür.

  • Strategy Pattern: Farklı ödeme yöntemlerinin seçilmesine olanak sağlar.

  • Factory Pattern: Doğru ödeme stratejisini veya bağdaştırıcısını oluşturur.

  • DTO Modeli: Ödeme talebi ve yanıt verilerini standartlaştırır.

  • Dependency Injection: Seçilen ödeme uygulamasını enjekte eder.

Bu, ödeme sağlayıcısının ayrıntılarını temel uygulama mantığından uzak tutar.

Ödeme Arayüzü ve DTO Örneği

readonly class PaymentRequestDto
{
    public function __construct(
        public float $amount,
        public string $currency,
        public string $customerEmail
    ) {
    }
}

readonly class PaymentResultDto
{
    public function __construct(
        public bool $successful,
        public ?string $transactionId,
        public ?string $message = null
    ) {
    }
}

interface PaymentGatewayInterface
{
    public function charge(PaymentRequestDto $request): PaymentResultDto;
}

Uygulama artık doğrudan sağlayıcıya özel istek ve yanıt formatlarına bağlı olmak yerine kararlı bir dahili arayüze ve DTOs'ya bağlı.

Ödeme Adaptersı Örneği

class StripePaymentAdapter implements PaymentGatewayInterface
{
    public function __construct(
        private StripeClient $client
    ) {
    }

    public function charge(PaymentRequestDto $request): PaymentResultDto
    {
        $response = $this->client->createCharge([
            'amount' => $request->amount,
            'currency' => $request->currency,
            'email' => $request->customerEmail,
        ]);

        return new PaymentResultDto(
            successful: $response['status'] === 'paid',
            transactionId: $response['id'] ?? null,
            message: $response['message'] ?? null
        );
    }
}

Bu adaptör Stripe'a özgü mantığı izole eder. Uygulama daha sonra başka bir ödeme sağlayıcısı eklerse yeni bir bağdaştırıcı aynı arayüzü uygulayabilir.

Gerçek Proje Örneği: Bildirim Sistemi

Bir bildirim sistemi e-posta, SMS, anlık bildirimler, Telegram, Slack veya WhatsApp yoluyla mesaj gönderebilir. Her kanalın farklı bir API'su olabilir.

Yararlı desenler şunları içerir:

  • Strategy Pattern: Bildirim kanalını seçer.

  • Adapter Pattern: Harici sağlayıcıları entegre eder.

  • Factory Pattern: Doğru göndereni oluşturur.

  • Decorator Pattern: Günlük kaydı, yeniden deneme veya hız sınırlaması ekler.

  • Command Pattern: Bildirim gönderimini sıraya koyar.

  • Observer Pattern: Olaylardan sonra bildirim gönderir.

Bu, uygulamanın bildirim iş akışını yeniden yazmaya gerek kalmadan yeni kanallar eklemesine olanak tanır.

Bildirim Strategysi Örneği

interface NotificationSender
{
    public function send(string $recipient, string $message): bool;
}

class EmailNotificationSender implements NotificationSender
{
    public function send(string $recipient, string $message): bool
    {
        // Send email
        return true;
    }
}

class SmsNotificationSender implements NotificationSender
{
    public function send(string $recipient, string $message): bool
    {
        // Send SMS
        return true;
    }
}

Uygulama, somut kanalı bilmeden NotificationSender'ı kullanabilir.

Gerçek Bildirim Sisteminde Decorator

Orijinal göndereni değiştirmeden günlük kaydı eklenebilir:

class LoggedNotificationSender implements NotificationSender
{
    public function __construct(
        private NotificationSender $sender,
        private LoggerInterface $logger
    ) {
    }

    public function send(string $recipient, string $message): bool
    {
        $this->logger->info('Sending notification', [
            'recipient' => $recipient,
        ]);

        return $this->sender->send($recipient, $message);
    }
}

Bu, Decorator Pattern'nun gerçek projelerde pratik bir kullanımıdır.

Gerçek Proje Örneği: Rapor Oluşturma

Rapor oluşturma genellikle veri alımını, filtrelemeyi, biçimlendirmeyi, dışa aktarmayı, depolamayı ve bildirimi içerir. PDF, Excel, CSV ve JSON gibi birden fazla formatı destekleyebilir.

Yararlı desenler şunları içerir:

  • Repository Pattern: Rapor verilerini alır.

  • Service Layer: Rapor oluşturmayı koordine eder.

  • Strategy Pattern: Dışa aktarma formatını seçer.

  • Factory Pattern: Doğru dışa aktarıcıyı oluşturur.

  • Command Pattern: Uzun süreli rapor oluşturmayı sıraya koyar.

  • Facade Pattern: Karmaşık rapor iş akışları için basit bir arayüz sağlar.

Bu yapı, yeni formatlar ve filtreler eklendikçe rapor oluşturmanın yönetilebilir kalmasına yardımcı olur.

İhracat Strategysi Örneği

interface ReportExporter
{
    public function export(array $data): string;
}

class PdfReportExporter implements ReportExporter
{
    public function export(array $data): string
    {
        return 'PDF content';
    }
}

class CsvReportExporter implements ReportExporter
{
    public function export(array $data): string
    {
        return 'CSV content';
    }
}

Her ihracat formatı ayrı bir strateji olarak uygulanır. Rapor hizmeti herhangi bir ihracatçıyı aynı arayüz üzerinden kullanabilir.

Gerçek Proje Örneği: Dosya Alma Sistemi

Dosya içe aktarma sistemleri genellikle validation, ayrıştırma, eşleme, kopya kontrolü, database kaydetme, hata raporlama ve arka planda işlemeye ihtiyaç duyar.

Yararlı desenler şunları içerir:

  • Strategy Pattern: CSV, Excel veya JSON gibi ayrıştırıcı türünü seçer.

  • DTO Desen: İçe aktarılan satırları temsil eder.

  • Repository Pattern: İçe aktarılan verileri kaydeder.

  • Service Layer: İçe aktarma iş akışını koordine eder.

  • Command Pattern: İçe aktarmayı sıraya alınmış bir iş olarak çalıştırır.

  • Observer Pattern: İçe aktarma tamamlandıktan sonra bildirimleri tetikler.

Bu, dosya içe aktarma mantığının tek bir büyük sınıfa dönüşmesini engeller.

Dosya Ayrıştırıcı Strategysi Örneği

interface FileParser
{
    public function parse(string $filePath): array;
}

class CsvFileParser implements FileParser
{
    public function parse(string $filePath): array
    {
        // Parse CSV file
        return [];
    }
}

class JsonFileParser implements FileParser
{
    public function parse(string $filePath): array
    {
        // Parse JSON file
        return [];
    }
}

İçe aktarma hizmeti bir ayrıştırıcı arabirimi kullanabilir ve her dosya biçiminin ayrıntılarını bilmesi gerekmez.

Gerçek Proje Örneği: Çok Kiracılı Uygulama

Çok kiracılı uygulamalar genellikle aynı uygulamadan birden fazla müşteriye veya kuruluşa hizmet verir. Kiracıya özel ayarlara, depolama yollarına, ödeme sağlayıcılarına, markalamaya, izinlere ve veri filtrelerine ihtiyaç duyabilirler.

Yararlı desenler şunları içerir:

  • Strategy Pattern: Kiracıya özgü davranışı seçer.

  • Factory Pattern: Kiracıya özel hizmetler oluşturur.

  • Repository Pattern: Kiracı veri filtrelemesini uygular.

  • Decorator Pattern: Kiracı kapsamını veya günlüğünü ekler.

  • Dependency Injection: Kiracıyı tanıyan hizmetler sağlar.

Design Patterns kiracı mantığının düzenli tutulmasına ve kiracı koşullarının her yere yayılmasının önlenmesine yardımcı olur.

Doğru Deseni SELECTk

Doğru modeli seçmek sorunu anlamakla başlar. Bir desen seçerek başlamayın. Kodu neyin zorlaştırdığını tanımlayarak başlayın.

Yararlı sorular şunları içerir:

  • Nesne oluşturma karmaşıklaşıyor mu? Factoryyı, Abstract Factoryyı, Builderyu veya Prototypei düşünün.

  • Arayüzler uyumsuz mu? Adapterü düşünün.

  • Bir sınıfı değiştirmeden davranış eklemeniz mi gerekiyor? Decoratorü düşünün.

  • Bir alt sistem doğrudan kullanılamayacak kadar karmaşık mı? Facade'yi düşünün.

  • Birden fazla algoritma veya davranış var mı? Strategyyi düşünün.

  • Bir etkinliğe birden fazla dinleyici tepki vermeli mi? Observer'ı düşünün.

  • Eylemler sıraya alınmalı mı, günlüğe kaydedilmeli mi yoksa geri alınmalı mı? Commanda'yı düşünün.

  • Veri erişim mantığı dağınık mı? Repositoryyu düşünün.

  • İş mantığı denetleyicilerin içinde mi? Service Layer'yu düşünün.

  • Ham diziler net değil mi? DTO'yu düşünün.

Desen problemle eşleşmelidir.

Desenler Birlikte Nasıl Çalışır?

Gerçek projelerde desenler nadiren tek başına kullanılır. Genellikle doğal olarak birlikte çalışırlar.

Örneğin, bir CheckoutService, Dependency Injection yoluyla enjekte edilen bir PaymentGatewayInterface'i kullanabilir. Ödeme ağ geçidi Stripe çevresinde bir Adapter olabilir. Seçilen ödeme yöntemi Strategy kullanılarak seçilebilir. Doğru strateji bir Factory tarafından oluşturulabilir. Son OrderPlaced olayı Observerleri bilgilendirebilir. Sipariş verildikten sonra SendInvoiceCommand kuyruğa alınabilir.

Bu, eğer her model gerçek bir sorunu çözüyorsa, projenin aşırı mühendislikle yapıldığı anlamına gelmez. Önemli olan, her sınıfın açık bir sorumluluğa sahip olması ve kodun anlaşılır kalmasıdır.

Örnek: Birlikte Çalışan Desenler

class CheckoutService
{
    public function __construct(
        private OrderRepositoryInterface $orders,
        private PaymentGatewayInterface $paymentGateway,
        private DiscountStrategy $discountStrategy,
        private EventDispatcherInterface $events
    ) {
    }

    public function checkout(CreateOrderDto $dto): Order
    {
        $discount = $this->discountStrategy->calculate($dto);

        $order = $this->orders->create($dto, $discount);

        $this->paymentGateway->charge(
            new PaymentRequestDto(
                amount: $order->total,
                currency: $order->currency,
                customerEmail: $order->customerEmail
            )
        );

        $this->events->dispatch(new OrderPlaced($order));

        return $order;
    }
}

Bu örnek, Service Layer, Repository, Strategy, Adapter tarzı PaymentGateway, DTO, Dependency Injection ve Observer'ı birleştirir. Her bağımlılığın açık bir rolü olduğundan kod anlaşılır kalır.

Laravel Projelerinde Design Patterns

Laravel, Design Patternsnin uygulanmasını kolaylaştıran birçok araç sağlar. Servis konteyneri Dependency Injection'yu destekler. Olaylar ve dinleyiciler Observer tarzı mimariyi destekler. İşler, Command tarzı arka plan çalışmasını destekler. Eloquent ihtiyaç duyulduğunda Repository Pattern ile çalışabilir. Hizmet sınıfları ve eylem sınıfları business logicnı düzenleyebilir.

Yaygın Laravel modeli kullanımı şunları içerir:

  • İş iş akışları için hizmet sınıfları.

  • validation için Form İstekleri.

  • Yapılandırılmış hizmet girişi için DTOs.

  • Karmaşık veri erişimi için depolar.

  • Yan etkiler için olaylar ve dinleyiciler.

  • Kuyruğa alınmış komutlar için işler.

  • Harici APIs için adaptörler.

  • Ödeme, nakliye, indirimler veya ihracat stratejileri.

Laravel her modeli zorlamaz, bu nedenle geliştiriciler bunları projenin karmaşıklığına göre kullanmalıdır.

Symfony Projelerinde Design Patterns

Symfony, hizmet odaklı mimariyi ve Dependency Injection'yu güçlü bir şekilde destekler. Birçok Symfony uygulaması doğal olarak hizmetleri, veri havuzlarını, olay abonelerini, komut işleyicilerini, DTOs'yu, bağdaştırıcıları ve stratejileri kullanır.

Yaygın Symfony modeli kullanımı şunları içerir:

  • Uygulama mantığına yönelik hizmetler.

  • Veri erişimi için doktrin depoları.

  • Observer tarzı davranış için olay gönderici.

  • Command ve CQRS tarzı iş akışları için Messenger bileşeni.

  • Formlar, APIs ve komutlar için DTOs.

  • Harici hizmetler için adaptörler.

  • collections stratejisine yönelik etiketli hizmetler.

Symfony'nun yapısı onu, kalıpların kodu düzenli tutmaya yardımcı olduğu büyük projeler için uygun hale getirir.

Design Patterns ve Temiz Mimari

Design Patterns, ayrı endişelere yardımcı olarak clean architecture'yu destekler. Temiz mimari, iş kurallarını çerçevelerden, databases'dan, kullanıcı arayüzlerinden ve harici hizmetlerden bağımsız tutmaya odaklanır.

Desenler bu hedefi destekleyebilir:

  • Repository arayüzleri business logicnı database ayrıntılarından korur.

  • Adapterler harici hizmetleri yalıtır.

  • DTOs veri aktarım sınırlarını tanımlar.

  • Hizmetler veya kullanım senaryoları uygulama iş akışlarını düzenler.

  • Dependency Injection dışarıdan uygulamalar sağlar.

  • Commandlar uygulama eylemlerini temsil eder.

  • Olaylar ve gözlemciler yan etkileri ayırır.

Ancak clean architecture her desenin kullanılmasını gerektirmez. Açık sınırlar ve iyi bir bağımlılık yönetimi gerektirir.

Design Patterns ve SOLID İlkeleri

Design Patterns ve SOLID ilkeleri yakından ilişkilidir. Birçok kalıp, SOLID ilkelerinin pratik kodda uygulanmasına yardımcı olur.

Örneğin:

  • Strategy Pattern Açık Kapalı Prensibini destekler.

  • Dependency Injection, Bağımlılığı Ters Çevirme İlkesini destekler.

  • Service Layer, Tek Sorumluluk İlkesinin desteklenmesine yardımcı olur.

  • Adapter Pattern arayüz tabanlı tasarımı destekler.

  • Decorator Pattern, mevcut sınıfları değiştirmeden davranışı genişletir.

  • Repository Pattern kalıcılık endişelerini ayırır.

SOLID'i anlamak, geliştiricilerin Design Patternsni daha akıllıca kullanmasına yardımcı olur.

Design Patterns ile aşırı mühendislik

Aşırı mühendislik, geliştiricilerin ihtiyaç duymayan bir sorun için çok fazla yapı eklemesi durumunda ortaya çıkar. Bu, kodun okunmasını zorlaştırabilir ve geliştirilmesini yavaşlatabilir.

Örneğin, basit bir iletişim formunun DTOs'ya, depolara, fabrikalara, stratejilere, komutlara ve olaylara ihtiyacı olmayabilir. Basit bir kontrolör ve servis yeterli olabilir.

Desenler gerçek bir sorunu çözdüklerinde eklenmelidir. Bir kalıp, netliği artırmadan yalnızca ekstra dosyalar ekliyorsa, buna gerek olmayabilir.

Aşırı Kullandığınıza İlişkin İşaretler Design Patterns

Design Patterns şu durumlarda aşırı kullanılabilir:

  • Basit özellikler çok fazla sınıf gerektirir.

  • Geliştiriciler yürütme akışını kolayca izleyemezler.

  • Çoğu sınıf, çağrıları değer katmadan yalnızca iletir.

  • Arayüzler, birden fazla uygulamaya veya teste ihtiyaç duymadan her sınıf için oluşturulur.

  • Kod daha soyut görünüyor ancak daha anlaşılır değil.

  • Küçük bir özellik eklemek, birçok gereksiz katmanın düzenlenmesini gerektirir.

İyi mimari değişimi basitleştirmeli, her değişikliği ağırlaştırmamalı.

Yetersiz Mühendislik: Tersi Sorun

Yetersiz yapı olmadığında yetersiz mühendislik meydana gelir. Her şey denetleyicilere, modellere veya yardımcı dosyalara yerleştirilir. Bu ilk başta hızlı gelebilir ancak daha sonra pahalı hale gelir.

Yetersiz mühendislik belirtileri şunlardır:

  • Çok büyük kontrolörler.

  • Tekrarlanan database sorguları.

  • Yinelenen business logic.

  • Harici API çağrıları her yere yayıldı.

  • Davranış seçimi için uzun if ve switch ifadeleri.

  • Test edilmesi zor dersler.

  • Bir özellikteki değişiklik ilgisiz özellikleri bozar.

Amaç dengedir. Projeyi sürdürülebilir kılmak için yeterli yapıyı kullanın, ancak basit işleri karmaşık hale getirecek kadar da fazla değil.

Pratik Kural: Basitten Başlayın, Kalıplara Doğru Yeniden Düzenleyin

Pratik bir yaklaşım, basit bir başlangıç yapmak ve ihtiyaç netleştiğinde kalıplara doğru yeniden düzenleme yapmaktır.

Örneğin:

  • Denetleyici çok büyürse mantığı bir hizmete taşıyın.

  • Sorgular tekrarlanıyorsa bir depo yöntemi oluşturun.

  • Ham diziler kafa karıştırıcı hale gelirse bir DTO kullanın.

  • Eğer birçok if ifadesi davranışı seçiyorsa, Strategyyi tanıtın.

  • Harici API kodu yayılırsa bir Adapter kullanın.

  • Bir eylemden sonra yan etkiler artarsa olayları ve gözlemcileri tanıtın.

  • Görevlerin daha sonra çalıştırılması gerekiyorsa komutları veya işleri tanıtın.

Bu yaklaşım mimariyi pratik tutar ve gereksiz karmaşıklığı önler.

Klasör Yapısı Örneği

Gerçek bir PHP veya Laravel projesi, aşağıdaki gibi klasörleri kullanarak kalıpları düzenleyebilir:

app/
  DTO/
    CreateOrderDto.php
    PaymentRequestDto.php

  Services/
    CheckoutService.php
    UserRegistrationService.php

  Repositories/
    OrderRepositoryInterface.php
    EloquentOrderRepository.php

  Strategies/
    Discounts/
      DiscountStrategy.php
      PremiumCustomerDiscountStrategy.php
      SeasonalDiscountStrategy.php

  Adapters/
    Payments/
      StripePaymentAdapter.php
      PayPalPaymentAdapter.php

  Events/
    OrderPlaced.php

  Listeners/
    SendOrderConfirmation.php
    UpdateAnalytics.php

  Jobs/
    GenerateInvoiceJob.php

Bu sadece bir örnek. En iyi klasör yapısı çerçeveye, ekip stiline ve proje boyutuna bağlıdır.

Design Patterns'nun Gerçek Projelerde Test Edilmesi

Design Patterns genellikle testi kolaylaştırır. Hizmetler sahte depolarla test edilebilir. Strategyler bağımsız olarak test edilebilir. Adapterlerle alay edilebilir. Commandlar tek eylemler olarak test edilebilir. DTOs, giriş verilerini öngörülebilir hale getirir.

Örneğin, bir indirim stratejisini test etmek basittir çünkü sınıfın odaklanmış tek bir sorumluluğu vardır:

class PremiumCustomerDiscountStrategyTest
{
    public function test_it_calculates_premium_discount(): void
    {
        $strategy = new PremiumCustomerDiscountStrategy();

        $discount = $strategy->calculate(200);

        assert($discount === 30.0);
    }
}

Küçük odaklı sınıfların test edilmesi genellikle büyük karma sınıflardan daha kolaydır.

Design Patterns ve Ekip İşbirliği

Design Patterns aynı zamanda geliştiriciler arasındaki iletişimi de geliştirir. Bir geliştirici “bu bir adaptör olmalı” dediğinde diğer geliştiriciler sınıfın harici bir arayüzü dahili bir arayüze çevirmesi gerektiğini anlar.

Bir geliştirici "burada bir strateji kullanın" dediğinde ekip, birbiriyle değiştirilebilir birden fazla davranışın olduğunu anlar.

Paylaşılan kalıp sözlüğü, kod incelemelerini, mimari tartışmalarını ve katılımı kolaylaştırır.

Gerçek Projelerde Yaygın Hatalar

Yaygın bir hata, kalıpları gerçek bir sorunu çözdükleri için değil, profesyonel göründükleri için kullanmaktır.

Bir diğer hata ise kalıpları kullanırken bile sorumlulukları karıştırmaktır. Örneğin, bir veri havuzu iş akışı mantığını içermemeli ve bir DTO karmaşık iş kuralları içermemelidir.

Üçüncü bir hata ise sebepsiz yere her şey için arayüz oluşturmaktır. Arayüzler birden fazla uygulama, test ihtiyacı veya mimari sınırlar olduğunda kullanışlıdır.

Dördüncü hata ise çerçeve kurallarının göz ardı edilmesidir. Laravel, Symfony ve diğer çerçeveler halihazırda yararlı yapılar sağlamaktadır. Desenler çerçeveye karşı değil, çerçeveyle birlikte çalışmalıdır.

Gerçek Projelerde Design Patterns Kullanımına İlişkin En İyi Uygulamalar

Design Patternsni etkili bir şekilde kullanmak için geliştiricilerin açıklığa ve sorumluluğa odaklanması gerekir.

Yararlı en iyi uygulamalar şunları içerir:

  • Desenle değil problemle başlayın.

  • Kalıpları yalnızca netliği veya esnekliği artırdıklarında kullanın.

  • Her sınıfın bir sorumluluğa odaklanmasını sağlayın.

  • Gerçek değer sağladıkları arayüzleri kullanın.

  • Gizli hizmet bulucuları yerine Dependency Injection kullanın.

  • Denetleyicileri ince tutun ve business logicnı hizmetlere veya kullanım senaryolarına taşıyın.

  • Anlamlı veri erişim mantığı için depoları kullanın.

  • Veri yapılarının netliğe ihtiyaç duyduğu durumlarda DTOs'yu kullanın.

  • Bağımsız yan etkiler için olayları kullanın.

  • Basit özellikler üzerinde aşırı mühendislik yapmaktan kaçının.

Bu uygulamalar, Design Patternsnın projeyi zorlaştırmak yerine desteklemesine yardımcı olur.

Desen Uygulamadan Önce Pratik Kontrol Listesi

Bir tasarım deseni uygulamadan önce şu soruları sorun:

  • Hangi spesifik sorunu çözüyorum?

  • Bu kalıp kodun anlaşılmasını kolaylaştıracak mı?

  • Çoğaltmayı veya birleştirmeyi azaltacak mı?

  • Gelecekteki değişiklikleri daha güvenli hale getirecek mi?

  • Testi kolaylaştıracak mı?

  • Özellik modeli haklı çıkaracak kadar karmaşık mı?

  • Başka bir geliştirici akışı kolayca anlayabilir mi?

  • Çerçeve kurallarına uyuyor muyum?

Bu soruların birçoğuna cevabınız evet ise desen iyi bir seçim olabilir.

Gerçek Projeler için Önerilen Öğrenme Yolu

Yeni başlayanlar ve orta düzey geliştiriciler için pratik bir öğrenme yolu:

  1. OOP temellerini öğrenin: sınıflar, nesneler, kapsülleme, kalıtım, çok biçimlilik ve soyutlama.

  2. Arayüzleri ve Dependency Injection'yu öğrenin.

  3. MVC ve Service Layer Desenini öğrenin.

  4. Repository Pattern ve DTO Desenini öğrenin.

  5. Esnek davranış için Factory ve Strategyyi öğrenin.

  6. Harici entegrasyonlar için Adapterü Öğrenin.

  7. Etkinlikler, işler ve iş akışları için Observer ve Commanda'yı öğrenin.

  8. Yapısal organizasyon için Decorator ve Facade öğrenin.

  9. Yalnızca teoriyi okuyarak değil, gerçek özellikleri de yeniden düzenleyerek pratik yapın.

Bu öğrenme yolu, geliştiricilerin temel OOP'dan gerçek proje mimarisine adım adım geçmelerine yardımcı olur.

Sonuç

Design Patterns, gerçek projelerdeki gerçek sorunları çözdüklerinde en kullanışlıdır. Geliştiricilerin business logicnı düzenlemesine, bağımlılıkları yönetmesine, harici sistemleri entegre etmesine, veri aktarımını yapılandırmasına, olayları yönetmesine, eylemleri sıraya koymasına ve uygulamaları büyüdükçe sürdürülebilir tutmasına yardımcı olurlar.

Service Layer, Repository, DTO, Factory, Strategy, Adapter, Decorator, Facade, Observer, Command ve Dependency Injection gibi desenler genellikle modern PHP, Laravel, Symfony ve kurumsal uygulamalarda birlikte çalışır.

Ancak Design Patterns dikkatli kullanılmalıdır. Kalıpların aşırı kullanılması gereksiz karmaşıklık yaratabilir, bunları göz ardı etmek ise dağınık ve kırılgan kodlara yol açabilir. En iyi yaklaşım, basit bir başlangıç ​​yapmak, gerçek tasarım sorunlarını belirlemek ve netliği, esnekliği ve sürdürülebilirliği iyileştirdiğinde doğru modele doğru yeniden düzenleme yapmaktır.