Meydan okuma - Tasarım desenleri
Factory, Strategy ve Observer'la esnek bir bildirim sistemi yazarsın.
Bildirim sistemi: üç desen
Bu derste esnek bir bildirim sistemi yazarsın. Factory deseni nesne oluşturmayı NotificationFactory::make($type) yöntiminde toplar; istemci somut sınıfı bilmeden kanal ister. Strategy deseni her kanalı aynı NotificationChannel arayüzüyle sunar; EmailChannel ve SmsChannel birbirinin yerine kullanılır.
Observer deseni olay yayını için NotificationObserver arayüzü ve attach() yöntemiyle çalışır. NotificationService::notify() bir kanal üretir, mesajı gönderir ve tüm gözlemcilere sonucu bildirir. Böylece kanal değişimi ya da log ekleme tek yerden yönetilir.
Observer deseni
Observer deseni bir konu (subject) ve ona abone olan dinleyicilerden oluşur. NotificationService konu; LogObserver dinleyicidir. attach(NotificationObserver $o) dinleyiciyi bir diziye ekler. notify() çağrıldığında her dinleyicinin onSent(string $log) yöntemi çalışır.
Böylece bildirim gönderildiğinde log, analitik veya e-posta gibi yan etkileri NotificationService'i değiştirmeden ekleyebilirsin. Yeni bir dinleyici yazıp attach() ile eklemen yeterli. Açık-kapalı ilkesi uygulanmış olur: sınıf değişikliğe kapalı, genişlemeye açıktır.
Uygulama NotificationFactory ve NotificationService yaz
Başlangıç kodunda arayüzler ve kanal sınıfları (EmailChannel, SmsChannel, LogObserver) hazır. Sen NotificationFactory ve NotificationService sınıflarını yazacaksın.
NotificationFactory::make(string $type): NotificationChannel statik yöntemi match ile "email" için EmailChannel, "sms" için SmsChannel döndürsün; varsayılan dal InvalidArgumentException fırlatsın. NotificationService sınıfı özel array $observers = [] tutsun; attach(NotificationObserver $o): void dinleyici eklesin; notify(string $type, string $message): string önce NotificationFactory::make ile kanal üretip send() çağırsın, sonra her gözlemcinin onSent($result) yöntemini çalıştırıp $result döndürsün.
Örnek çözümü göster
NotificationChannel { return match($type) { "email" => new EmailChannel(), "sms" => new SmsChannel(), default => throw new InvalidArgumentException(...) }; } } yaz. class NotificationService { private array $observers = []; public function attach(NotificationObserver $o): void { ... } public function notify(string $type, string $message): string { $channel = NotificationFactory::make($type); $result = $channel->send($message); foreach ($this->observers as $o) { $o->onSent($result); } return $result; } } yaz. Sonra demoyu çalıştır.
Örnek çözüm:
<?php
// Ürün API'si + güvenlik + bildirimler: NotificationFactory ve NotificationService yaz
class ProductRepository {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
$this->pdo->exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)");
$stmt = $this->pdo->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
$stmt->execute(["Kale", 5]);
$stmt->execute(["Defter", 15]);
}
public function all(): array {
$rows = $this->pdo->query("SELECT id, name, price FROM products ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
return array_map(fn($r) => [
"id" => (int)$r["id"],
"name" => $r["name"],
"price" => (int)$r["price"]
], $rows);
}
}
function handleGet(PDO $pdo): string {
$repo = new ProductRepository($pdo);
return json_encode(["products" => $repo->all()], JSON_UNESCAPED_UNICODE);
}
class AuthService {
private array $users = [];
private string $csrf = "";
public function register(string $user, string $pass): void {
$this->users[$user] = password_hash($pass, PASSWORD_DEFAULT);
}
public function login(string $user, string $pass): bool {
if (!isset($this->users[$user])) return false;
return password_verify($pass, $this->users[$user]);
}
public function csrfToken(): string {
if ($this->csrf === "") {
$this->csrf = bin2hex(random_bytes(8));
}
return $this->csrf;
}
public function applyHeaders(): void {
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
}
}
interface NotificationChannel {
public function send(string $message): string;
}
class EmailChannel implements NotificationChannel {
public function send(string $message): string {
return "E-posta: " . $message;
}
}
class SmsChannel implements NotificationChannel {
public function send(string $message): string {
return "SMS: " . $message;
}
}
interface NotificationObserver {
public function onSent(string $log): void;
}
class LogObserver implements NotificationObserver {
public array $logs = [];
public function onSent(string $log): void {
$this->logs[] = $log;
}
}
class NotificationFactory {
public static function make(string $type): NotificationChannel {
return match ($type) {
"email" => new EmailChannel(),
"sms" => new SmsChannel(),
default => throw new InvalidArgumentException("Bilinmeyen kanal"),
};
}
}
class NotificationService {
private array $observers = [];
public function attach(NotificationObserver $o): void {
$this->observers[] = $o;
}
public function notify(string $type, string $message): string {
$channel = NotificationFactory::make($type);
$result = $channel->send($message);
foreach ($this->observers as $o) {
$o->onSent($result);
}
return $result;
}
}
// Demo:
$svc = new NotificationService();
$logger = new LogObserver();
$svc->attach($logger);
echo $svc->notify("email", "Sipariş alındı") . "\n";
echo $svc->notify("sms", "Kargo yola çıktı") . "\n";
echo "Log sayısı: " . count($logger->logs) . "\n";
Kendini test et
-
Hangi tasarım deseni nesne oluşturmayı merkezi bir yere toplar?
- Observer
- Strategy
- Singleton
- Factory
Cevabı göster
Factory — Doğru. Factory deseni nesne oluşturmayı tek bir yere toplar; istemci hangi sınıfın örneklendiğini bilmez.
-
NotificationChannel arayüzünü tamamla
interface NotificationChannel { public function ____(string $message): string; }Cevabı göster
interface NotificationChannel { public function send(string $message): string; } -
Tasarım desenlerini eşleştir
- Factory
- Nesne oluşturmayı toplar
- Strategy
- Davranışı çalışma anında seçer
- Observer
- Olay aboneliği bildirir
- Interface
- Sözleşme tanımlar