Tek Sorumluluk Prensibi (SRP), bir sınıfın değişmesi için birden fazla sebep olmaması gerektiğini belirtir. Diğer bir deyişle, her sınıf yalnızca bir sorumluluğa sahip olmalıdır. Bir modül, yalnızca bir aktörden sorumlu olmalıdır. Aktör terimi, modülde bir değişiklik talep eden bir veya daha fazla paydaşı veya kullanıcı grubunu ifade eder.
Basitçe: Bir şey yap, ve bunu iyi yap!
Gerçek Dünya Örneği: Bir restorana aşçı alıyorsunuz.
Gerçek Dünya Örneği: Bir restorana aşçı alıyorsunuz.
- Kötü Tasarım (Her Şeyi Yapan Aşçı): Mutfakta yemek pişirmek, bulaşıkları yıkamak, restoran vergilerini hesaplamak ve borular patladığında onarımlar yapmak zorunda olan bir aşçı alıyorsunuz.
- Sorun: Vergi yasaları değişirse, aşçınız yemek pişirmek yerine muhasebe çalışmalarıyla meşguldür. Eğer borularla ilgili bir sorun olursa, mutfak sular altında kalır ve tüm restoran kapanır. Tek bir değişiklik her şeyi mahveder.
- İyi Tasarım (SRP Uygulanmış): Yemek pişirmek için bir aşçı, temizliği sağlamak için bir bulaşıkçı, vergi işlemleri için bir muhasebeci ve onarımlar için bir tesisatçı ile çalışıyorsunuz.
- Fayda: Vergi yasaları değişse bile yalnızca muhasebecinin iş akışı değişir. Aşçı, kesintisiz yemek pişirmeye devam eder.
❌ Yanlış Yaklaşım: Çok fazla iş yapan bir sınıf
❌ Yanlış Yaklaşım: Çok fazla iş yapan bir sınıf
Bu User sınıfı, kullanıcı verilerini, veritabanı bağlantılarını ve e-posta teslimatını yönetmektedir. Üç tane değişim sebebi vardır.
class User {
private $name;
private $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
// İş 1: Kullanıcı Verilerini Yönetmek
public function getName() { return $this->name; }
// İş 2: Veritabanı İşlemleri (Değişim sebebi: Veritabanı türlerini değiştirmek)
public function saveToDatabase() {
echo "Kullanıcı veritabanına kaydediliyor..."\n";
}
// İş 3: Bildirim Mantığı (Değişim sebebi: E-postadan SMS'e geçiş)
public function sendWelcomeEmail() {
echo "E-posta gönderiliyor: " . $this->email . ""\n";
}
}
✅ Doğru Yaklaşım: Sorumlulukları Bölmek
✅ Doğru Yaklaşım: Sorumlulukları Bölmek
Büyük sınıfı üç küçük, uzmanlaşmış sınıfa bölüyoruz. Her biri yalnızca bir işe sahiptir.
// 1. Yalnızca kullanıcı verisi temsilinden sorumlu
class User {
private $name;
private $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
public function getName() { return $this->name; }
public function getEmail() { return $this->email; }
}
// 2. Yalnızca verileri kaydetmekten sorumlu
class UserRepository {
public function save(User $user) {
echo "Kaydediliyor: " . $user->getName() . " veritabanına."\n";
}
}
// 3. Yalnızca mesaj göndermekten sorumlu
class EmailService {
public function sendWelcome(User $user) {
echo "E-posta gönderiliyor: " . $user->getEmail() . ""\n";
}
}
Bunun size sağladığı avantajlar
Bunun size sağladığı avantajlar
- Bakım Kolaylığı: Sınıflar tek bir iyi tanımlanmış sorumluluğa sahip olduğunda, bunlar daha kolay anlaşılır ve değiştirilebilir hale gelir.
- Test Edilebilirlik: Tek bir odakla yazılmış sınıfların birim testlerini yazmak daha kolaydır.
- Esneklik: Bir sorumluluktaki değişiklikler, sistemin alakasız bölümlerini etkilemez.
- Yeniden Kullanılabilirlik: EmailService, siparişler, sıfırla veya diğer özellikler için e-posta göndermek üzere yeniden kullanılabilir.
SRP uygulaması için ayıklanan 3 ana kafa karışıklığı
SRP uygulaması için ayıklanan 3 ana kafa karışıklığı
1. Kafa Karışıklığı: “Tek ‘sorumluluk’ bir sınıfın yalnızca bir yönteme sahip olması anlamına mı geliyor?”
1. Kafa Karışıklığı: “Tek ‘sorumluluk’ bir sınıfın yalnızca bir yönteme sahip olması anlamına mı geliyor?”
- Gerçek: Hayır. Bir sorumluluk, tek bir eylem değil, bir iş rolüdür.
- Örnek: Bir UserRepository sınıfı, save(), delete(), findById() ve update() gibi yöntemlere sahip olabilir. Bu hala bir sorumluluktur: Kullanıcı verisi depolamanın yönetimi.
2. Kafa Karışıklığı: “Daha önce değişmek için ‘sebep’ olarak neyi tanımlar?”
2. Kafa Karışıklığı: “Daha önce değişmek için ‘sebep’ olarak neyi tanımlar?”
- Gerçek: Robert C. Martin (SOLID’in yaratıcısı) “değişim sebebi”nin insanları değil, teknolojiyi ifade ettiğini belirtti. Bir sorumluluk, değişikliği talep edenler (aktörler) tarafından belirlenir.
- Örnek: Eğer Muhasebe departmanı bir değişiklik talep ederse ve İK departmanı bir değişiklik talep ederse, bunlar iki farklı aktördür. Aynı PHP sınıfını asla paylaşmamalıdırlar.
3. Kafa Karışıklığı: “Ne zaman sınıfları bölmeyi bırakmalıyım?”
3. Kafa Karışıklığı: “Ne zaman sınıfları bölmeyi bırakmalıyım?”
- Gerçek: Mühendisler genellikle kodu gereğinden fazla parçalamak için acele ederler, böylece UserEmailValidator, UserPasswordValidator ve UserAgeValidator oluşturarak “analiz felci” yaratırlar.
3 Adımlı Karar Matrisi (Nasıl Seçilir)
3 Adımlı Karar Matrisi (Nasıl Seçilir)
Bir PHP sınıfına bakarken, onu bölmek isteyip istemediğinizi belirlemek için kendinize aşağıdaki üç soruyu sorun:
Bu sınıf birden fazla iş departmanına/aktöre hizmet ediyor mu?
START SRP_Decision_Process
IF Class_Serves_Multiple_Departments_Or_Actors IS True THEN
EXECUTE Split_Class_Immediately
ELSE
IF Class_Mixes_Different_Technical_Layers IS True THEN
EXECUTE Split_Class
ELSE
EXECUTE Leave_It_Alone
END IF
END IF
END SRP_Decision_Process
- Bu özelliği kim talep ediyor? Eğer pazarlama ekibinin e-posta düzeni için bir hata düzeltmesi yanlışlıkla muhasebe ekibinin fatura hesaplamasını bozuyorsa, sınıfınız fazladan sorumluluğa sahiptir.
- Farklı teknik katmanlar karıştırılıyor mu? Eğer tek bir PHP sınıfı, ham SQL sorguları (SELECT *), HTML biçimlendirmesi () ve doğrulama mantığı (if (empty)) içeriyorsa, SRP'yi ihlal ediyordur. Bunları bölün.
- Sınıfı "VE" kelimesini kullanmadan tanımlayabilir miyim?
- Kötü: "Bu sınıf kullanıcı verilerini tutar ve bunu MySQL'e kaydeder ve Slack bildirimi gönderir." (3 sorumluluk)
- İyi: "Bu sınıf Slack uyarılarını gönderir." (1 sorumluluk)
Gerçek Dünya Karar Kuralı: "Zarar gelmeden bölme"Aşırı mühendislikten kaçınmak için, Taktiği SRP Kuralına göre ilerleyin:
- Küçük bir özellik varsa kodunuzu tek bir sınıf halinde yazmaya başlayın.
- Eğer bir parçayı (örneğin, e-posta mantığını başka bir yerde kullanmak) yeniden kullanmanız gerektiği anda, onu kendi sınıfına çıkartın.
- Bir sınıf 200 satırı geçtikten sonra, yukarıdaki karar matrisini kullanarak denetleyin.
Laravel Planı: SRP'yi Ustaca KullanmaKlasik bir Laravel hatasına bakalım: Kullanıcı kaydı mantığını, veritabanı eklemeyi, profil fotoğrafı manipülasyonunu ve hoş geldin bildirimlerini tek bir Kontrolcü metodunda koymak.
❌ Junior Yaklaşım: "Her şeyi yapan" Kontrolcü (SRP'yi İhlal Eder)Bu kontrolcü metodunun veritabanı değişiklikleri, doğrulama kuralları, resim optimizasyon değişiklikleri ve bildirim taşıyıcı değişiklikleri için dört ayrı nedeni vardır.
namespace App\Http\Controllers; use App\Models\User; use Illuminate\Http\Request; use Intervention\Image\Facades\Image; use Illuminate\Support\Facades\Mail; class RegisterController extends Controller { public function store(Request $request) { // Değişim sebebi 1: Doğrulama kuralları değişiyor $request->validate([ 'name' => 'required', 'email' => 'required|email|unique:users', 'avatar' => 'required|image' ]); // Değişim sebebi 2: Resim manipülasyonu sürecini yönetmek $avatar = $request->file('avatar'); $filename = time() . $avatar->getClientOriginalExtension(); Image::make($avatar)->resize(300, 300)->save(public_path('/uploads/' . $filename)); // Değişim sebebi 3: Veritabanı mimarisi güncellemeleri $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'avatar' => $filename, ]); // Değişim sebebi 4: E-postadan Slack/SMS'e geçiş Mail::to($user->email)->send(new \App\Mail\WelcomeMail($user)); return response()->json(['message' => 'Kullanıcı başarıyla oluşturuldu!'], 201); } }
Laravel Ustalığı Yaklaşımı (SRP'ye Uygun)Bunu bir framework mimarlığı gibi inşa etmek için, görevleri özel Laravel özellikleri kullanarak ayırıyoruz: Form İstekleri, Servis Sınıfları ve Olaylar/Dinleyiciler.
Adım 1: Doğrulamayı Form İsteği ile YönetmeKontrolcünün doğrulama kurallarını bilmesine gerek yoktur. Bunu özel bir istek dosyasına devrediyoruz.
namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class RegisterRequest extends FormRequest { public function rules(): array { return [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'avatar' => 'required|image|max:2048' ]; } }
Adım 2: Temel Mantığı Eylem/Servis Sınıfına ÇıkarmaKullanıcı kaydı ve dosya işlemesi görevini yürüten bir PHP sınıfı oluşturuyoruz.
namespace App\Actions; use App\Models\User; use App\Events\UserRegistered; use Illuminate\Http\UploadedFile; class RegisterNewUser { public function execute(array $data, UploadedFile $avatar): User { // Dosya yükleme mantığını temiz bir şekilde yönet $path = $avatar->store('avatars', 'public'); $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'avatar' => $path, ]); // Olayı tetikleyin! Bu sınıf, bildirimlerin nasıl gönderildiğini bilmez. event(new UserRegistered($user)); return $user; } }
Adım 3: İletişimi Olaylar & Dinleyiciler Aracılığıyla YönetmeKullanıcı kaydedildiğinde, bir olay tetiklenir. Özel bir Dinleyici bunu yakalar ve e-postayı asenkron olarak gönderir.
namespace App\Listeners; use App\Events\UserRegistered; use App\Mail\WelcomeMail; use Illuminate\Support\Facades\Mail; use Illuminate\Contracts\Queue\ShouldQueue; class SendWelcomeNotification implements ShouldQueue { public function handle(UserRegistered $event): void { // Bu sınıf yalnızca bildirim gönderme yöntemi değiştiğinde değişir Mail::to($event->user->email)->send(new WelcomeMail($event->user)); } }
Adım 4: Temiz, İnce Ana KontrolcüArtık kontrolcüye bakalım. Yalnızca bir sorumluluğa sahiptir: HTTP isteğini almak ve bir HTTP yanıtı döndürmek. Yüksek oranda okunabilir ve dış hatalardan kapalıdır.
namespace App\Http\Controllers; use App\Http\Requests\RegisterRequest; use App\Actions\RegisterNewUser; class RegisterController extends Controller { public function store(RegisterRequest $request, RegisterNewUser $registerAction) { // Yürütme, uzman çalışanlara devredildi $user = $registerAction->execute( $request->validated(), $request->file('avatar') ); return response()->json(['user' => $user], 201); } }
Neden bu sizi Laravel Ustası yapar?- Test Kolaylığı: RegisterNewUser için sahte bir yüklenmiş dosya geçirerek birim testi yazabilirsiniz, HTTP yönlendiricisini çalıştırmadan veya bir web tarayıcısı gibi davranmadan.
- Kusursuz Yeniden Kullanım: Bir kullanıcıyı bir API Uç Noktası, bir Blade Web Formu veya bir Terminal Artisan Komutu aracılığıyla kaydetmeniz gerektiğinde, yalnızca RegisterNewUser eylem sınıfını enjekte edip çalıştırırsınız. Asla kodu çoğaltmak zorunda kalmazsınız.
Kaynak: Orijinal Makale
- Sınıfı "VE" kelimesini kullanmadan tanımlayabilir miyim?


