أنماط التصميم: الاستراتيجية والمصنع
فهم أنماط التصميم
أنماط التصميم هي حلول قابلة لإعادة الاستخدام للمشاكل الشائعة في تصميم البرمجيات. تمثل أفضل الممارسات وتوفر مفردات مشتركة للمطورين. في هذا الدرس، سنستكشف أنماط الاستراتيجية والمصنع في سياق تطبيقات Laravel.
نمط الاستراتيجية (Strategy Pattern)
يحدد نمط الاستراتيجية عائلة من الخوارزميات، ويغلف كل واحدة، ويجعلها قابلة للتبديل. تسمح الاستراتيجية للخوارزمية بالتغير بشكل مستقل عن العملاء الذين يستخدمونها.
المشكلة: معالجة المدفوعات
تحتاج لدعم طرق دفع متعددة (بطاقة ائتمان، PayPal، Stripe) وتريد إضافة طرق جديدة بسهولة دون تعديل الكود الموجود.
// ❌ بدون نمط الاستراتيجية - ينتهك OCP
class PaymentController extends Controller
{
public function process(Request $request)
{
$method = $request->input('payment_method');
if ($method === 'credit_card') {
// منطق بطاقة الائتمان
$result = $this->processCreditCard($request->all());
} elseif ($method === 'paypal') {
// منطق PayPal
$result = $this->processPayPal($request->all());
} elseif ($method === 'stripe') {
// منطق Stripe
$result = $this->processStripe($request->all());
}
// إضافة طرق جديدة تتطلب تعديل هذا الكود
return response()->json($result);
}
}
// ✅ مع نمط الاستراتيجية - نظيف وقابل للتوسيع
namespace App\PaymentStrategies;
interface PaymentStrategy
{
public function pay(float $amount, array $details): PaymentResult;
public function refund(string $transactionId, float $amount): bool;
public function getName(): string;
}
class CreditCardStrategy implements PaymentStrategy
{
public function pay(float $amount, array $details): PaymentResult
{
// التحقق من تفاصيل بطاقة الائتمان
$cardNumber = $details['card_number'];
$cvv = $details['cvv'];
$expiryDate = $details['expiry_date'];
// معالجة الدفع عبر بوابة بطاقة الائتمان
$transactionId = 'CC-' . uniqid();
return new PaymentResult(
success: true,
transactionId: $transactionId,
message: 'دفع بطاقة الائتمان ناجح'
);
}
public function refund(string $transactionId, float $amount): bool
{
// منطق الاسترداد لبطاقة الائتمان
return true;
}
public function getName(): string
{
return 'credit_card';
}
}
class PayPalStrategy implements PaymentStrategy
{
public function pay(float $amount, array $details): PaymentResult
{
// تكامل PayPal API
$email = $details['email'];
$transactionId = 'PP-' . uniqid();
return new PaymentResult(
success: true,
transactionId: $transactionId,
message: 'دفع PayPal ناجح'
);
}
public function refund(string $transactionId, float $amount): bool
{
// منطق استرداد PayPal
return true;
}
public function getName(): string
{
return 'paypal';
}
}
class StripeStrategy implements PaymentStrategy
{
public function pay(float $amount, array $details): PaymentResult
{
// تكامل Stripe API
$token = $details['stripe_token'];
$transactionId = 'ST-' . uniqid();
return new PaymentResult(
success: true,
transactionId: $transactionId,
message: 'دفع Stripe ناجح'
);
}
public function refund(string $transactionId, float $amount): bool
{
// منطق استرداد Stripe
return true;
}
public function getName(): string
{
return 'stripe';
}
}
// صف السياق الذي يستخدم الاستراتيجية
class PaymentProcessor
{
private PaymentStrategy $strategy;
public function setStrategy(PaymentStrategy $strategy): void
{
$this->strategy = $strategy;
}
public function processPayment(float $amount, array $details): PaymentResult
{
if (!isset($this->strategy)) {
throw new \RuntimeException('لم يتم تعيين استراتيجية الدفع');
}
// تسجيل محاولة الدفع
Log::info('معالجة الدفع', [
'method' => $this->strategy->getName(),
'amount' => $amount,
]);
$result = $this->strategy->pay($amount, $details);
// تسجيل النتيجة
if ($result->success) {
Log::info('الدفع ناجح', [
'transaction_id' => $result->transactionId,
]);
}
return $result;
}
public function processRefund(string $transactionId, float $amount): bool
{
return $this->strategy->refund($transactionId, $amount);
}
}
// المتحكم يستخدم نمط الاستراتيجية
class PaymentController extends Controller
{
public function __construct(private PaymentProcessor $processor) {}
public function process(Request $request)
{
$method = $request->input('payment_method');
// اختيار الاستراتيجية بناءً على طريقة الدفع
$strategy = match ($method) {
'credit_card' => new CreditCardStrategy(),
'paypal' => new PayPalStrategy(),
'stripe' => new StripeStrategy(),
default => throw new \InvalidArgumentException('طريقة دفع غير صالحة'),
};
$this->processor->setStrategy($strategy);
$result = $this->processor->processPayment(
$request->input('amount'),
$request->except(['payment_method', 'amount'])
);
return response()->json($result);
}
}
نمط المصنع (Factory Pattern)
يوفر نمط المصنع واجهة لإنشاء الكائنات في صف أساسي، لكنه يسمح للصفوف الفرعية بتغيير نوع الكائنات التي سيتم إنشاؤها. يغلف منطق إنشاء الكائنات.
نمط المصنع البسيط
// نظام توليد التقارير مع المصنع البسيط
namespace App\Reports;
interface ReportInterface
{
public function generate(array $data): string;
public function getFormat(): string;
}
class PdfReport implements ReportInterface
{
public function generate(array $data): string
{
// توليد PDF باستخدام مكتبة مثل DomPDF
$pdf = PDF::loadView('reports.template', $data);
return $pdf->output();
}
public function getFormat(): string
{
return 'pdf';
}
}
class ExcelReport implements ReportInterface
{
public function generate(array $data): string
{
// توليد Excel باستخدام مكتبة مثل PhpSpreadsheet
return Excel::raw(new ReportExport($data), \Maatwebsite\Excel\Excel::XLSX);
}
public function getFormat(): string
{
return 'xlsx';
}
}
class CsvReport implements ReportInterface
{
public function generate(array $data): string
{
$output = fopen('php://temp', 'r+');
// كتابة الرؤوس
if (!empty($data)) {
fputcsv($output, array_keys($data[0]));
}
// كتابة البيانات
foreach ($data as $row) {
fputcsv($output, $row);
}
rewind($output);
$csv = stream_get_contents($output);
fclose($output);
return $csv;
}
public function getFormat(): string
{
return 'csv';
}
}
class HtmlReport implements ReportInterface
{
public function generate(array $data): string
{
return view('reports.html', ['data' => $data])->render();
}
public function getFormat(): string
{
return 'html';
}
}
// المصنع البسيط
class ReportFactory
{
public static function create(string $format): ReportInterface
{
return match (strtolower($format)) {
'pdf' => new PdfReport(),
'excel', 'xlsx' => new ExcelReport(),
'csv' => new CsvReport(),
'html' => new HtmlReport(),
default => throw new \InvalidArgumentException("تنسيق غير مدعوم: {$format}"),
};
}
}
// الاستخدام في المتحكم
class ReportController extends Controller
{
public function generate(Request $request)
{
$format = $request->input('format', 'pdf');
$data = $this->getReportData();
// المصنع ينشئ كائن التقرير المناسب
$report = ReportFactory::create($format);
$content = $report->generate($data);
return response($content)
->header('Content-Type', $this->getContentType($format))
->header('Content-Disposition', "attachment; filename=report.{$report->getFormat()}");
}
private function getReportData(): array
{
return User::select('name', 'email', 'created_at')->get()->toArray();
}
private function getContentType(string $format): string
{
return match ($format) {
'pdf' => 'application/pdf',
'excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'csv' => 'text/csv',
'html' => 'text/html',
default => 'application/octet-stream',
};
}
}
نمط طريقة المصنع (Factory Method)
يحدد نمط طريقة المصنع واجهة لإنشاء الكائنات، لكنه يترك للصفوف الفرعية قرار الصف الذي سيتم إنشاؤه. يفوض الإنشاء للصفوف الفرعية.
// نظام إشعارات مع نمط طريقة المصنع
namespace App\Notifications;
abstract class NotificationFactory
{
// طريقة المصنع (يتم تنفيذها بواسطة الصفوف الفرعية)
abstract protected function createNotification(): NotificationChannel;
// طريقة القالب التي تستخدم طريقة المصنع
public function send(string $recipient, string $message): bool
{
$notification = $this->createNotification();
// المعالجة المسبقة
$this->logAttempt($recipient, $message);
// الإرسال عبر القناة المناسبة
$result = $notification->send($recipient, $message);
// المعالجة اللاحقة
$this->logResult($result);
return $result;
}
protected function logAttempt(string $recipient, string $message): void
{
Log::info('محاولة إشعار', [
'channel' => $this->createNotification()->getName(),
'recipient' => $recipient,
]);
}
protected function logResult(bool $success): void
{
Log::info('نتيجة الإشعار', ['success' => $success]);
}
}
interface NotificationChannel
{
public function send(string $recipient, string $message): bool;
public function getName(): string;
}
class EmailChannel implements NotificationChannel
{
public function send(string $recipient, string $message): bool
{
Mail::raw($message, function ($mail) use ($recipient) {
$mail->to($recipient);
});
return true;
}
public function getName(): string
{
return 'email';
}
}
class SmsChannel implements NotificationChannel
{
public function send(string $recipient, string $message): bool
{
// تكامل SMS API (مثل Twilio)
return true;
}
public function getName(): string
{
return 'sms';
}
}
class PushChannel implements NotificationChannel
{
public function send(string $recipient, string $message): bool
{
// خدمة الإشعارات الفورية (مثل Firebase)
return true;
}
public function getName(): string
{
return 'push';
}
}
// المصانع الملموسة
class EmailNotificationFactory extends NotificationFactory
{
protected function createNotification(): NotificationChannel
{
return new EmailChannel();
}
}
class SmsNotificationFactory extends NotificationFactory
{
protected function createNotification(): NotificationChannel
{
return new SmsChannel();
}
}
class PushNotificationFactory extends NotificationFactory
{
protected function createNotification(): NotificationChannel
{
return new PushChannel();
}
}
// الاستخدام
class NotificationService
{
public function notify(string $channel, string $recipient, string $message): bool
{
$factory = match ($channel) {
'email' => new EmailNotificationFactory(),
'sms' => new SmsNotificationFactory(),
'push' => new PushNotificationFactory(),
default => throw new \InvalidArgumentException("قناة غير معروفة: {$channel}"),
};
return $factory->send($recipient, $message);
}
}
نمط المصنع المجرد (Abstract Factory)
يوفر نمط المصنع المجرد واجهة لإنشاء عائلات من الكائنات ذات الصلة أو التابعة دون تحديد صفوفها الملموسة.
// مصنع مكونات واجهة المستخدم لثيمات مختلفة
namespace App\UI;
// واجهة المصنع المجرد
interface UIFactory
{
public function createButton(): Button;
public function createInput(): Input;
public function createCard(): Card;
}
// واجهات المنتجات
interface Button
{
public function render(): string;
}
interface Input
{
public function render(string $name, string $value = ''): string;
}
interface Card
{
public function render(string $title, string $content): string;
}
// المنتجات الملموسة لثيم Material
class MaterialButton implements Button
{
public function render(): string
{
return '<button class="mdc-button mdc-button--raised"></button>';
}
}
class MaterialInput implements Input
{
public function render(string $name, string $value = ''): string
{
return '<div class="mdc-text-field">
<input type="text" name="' . $name . '" value="' . $value . '">
</div>';
}
}
class MaterialCard implements Card
{
public function render(string $title, string $content): string
{
return '<div class="mdc-card">
<h2>' . $title . '</h2>
<p>' . $content . '</p>
</div>';
}
}
// المنتجات الملموسة لثيم Bootstrap
class BootstrapButton implements Button
{
public function render(): string
{
return '<button class="btn btn-primary"></button>';
}
}
class BootstrapInput implements Input
{
public function render(string $name, string $value = ''): string
{
return '<input type="text" name="' . $name . '" value="' . $value . '" class="form-control">';
}
}
class BootstrapCard implements Card
{
public function render(string $title, string $content): string
{
return '<div class="card">
<div class="card-body">
<h5 class="card-title">' . $title . '</h5>
<p class="card-text">' . $content . '</p>
</div>
</div>';
}
}
// المصانع الملموسة
class MaterialUIFactory implements UIFactory
{
public function createButton(): Button
{
return new MaterialButton();
}
public function createInput(): Input
{
return new MaterialInput();
}
public function createCard(): Card
{
return new MaterialCard();
}
}
class BootstrapUIFactory implements UIFactory
{
public function createButton(): Button
{
return new BootstrapButton();
}
public function createInput(): Input
{
return new BootstrapInput();
}
public function createCard(): Card
{
return new BootstrapCard();
}
}
// كود العميل
class FormBuilder
{
public function __construct(private UIFactory $factory) {}
public function buildLoginForm(): string
{
$html = '<form>';
$html .= $this->factory->createInput()->render('email');
$html .= $this->factory->createInput()->render('password');
$html .= $this->factory->createButton()->render();
$html .= '</form>';
return $html;
}
}
// الاستخدام - تبديل الثيمات بسهولة
$theme = config('app.ui_theme', 'bootstrap');
$factory = match ($theme) {
'material' => new MaterialUIFactory(),
'bootstrap' => new BootstrapUIFactory(),
default => new BootstrapUIFactory(),
};
$formBuilder = new FormBuilder($factory);
echo $formBuilder->buildLoginForm();
- أنشئ واجهة ImageProcessorStrategy مع طريقة ()process
- نفذ ResizeStrategy، CropStrategy، WatermarkStrategy
- أنشئ صف سياق ImageProcessor
- اسمح بربط استراتيجيات متعددة
- دعم تصدير MySQL، PostgreSQL، SQLite
- كل مصدّر يجب أن يحتوي على طرق ()backup و ()restore
- استخدم نمط طريقة المصنع
- أضف خيار الضغط لجميع المصدّرين
- أنشئ عائلات لاستجابات JSON API و XML API
- كل عائلة يجب أن تنشئ: SuccessResponse، ErrorResponse، PaginatedResponse
- نفذ JsonApiFactory و XmlApiFactory
- استخدمهم في متحكم لتنسيق الاستجابات