طرق دفع بواجهة واحدة
Payment Methods with One Interface
طرق دفع بواجهة واحدة — Payment Methods with One Interface
الواجهة في Go تصبح مفيدة عندما تريد أن يتعامل جزء من النظام مع “سلوك” وليس مع نوع محدد. في هذا الدرس سنجعل عملية الدفع تقبل بطاقة أو تحويل بنكي بنفس الدالة.
فكر في صفحة دفع بسيطة. المستخدم قد يدفع ببطاقة، تحويل، نقداً عند الاستلام، أو محفظة رقمية. لو كتبت شرطاً لكل طريقة داخل checkout فستكبر الدالة بسرعة، وسيصبح كل نوع جديد سبباً لتعديل كود قديم. الواجهة تحل هذه المشكلة عندما تصف السلوك المطلوب فقط: “أي شيء يستطيع تنفيذ Pay يمكن استخدامه كطريقة دفع”.
في Go لا تحتاج أن تكتب إعلاناً يقول إن CardPayment implements PaymentMethod. يكفي أن تملك method بنفس التوقيع. هذا الأسلوب يسمى implicit implementation، وهو يجعل الواجهات خفيفة ومناسبة عندما تصممها من جهة المستهلك لا من جهة النوع. سنبدأ بنوع واحد، ثم نستخرج السلوك، ثم نضيف نوعاً جديداً بدون تغيير دالة الدفع.
الخطوة 1: ابدأ بدون واجهة
هذا الكود يعمل، لكنه يربط checkout بالبطاقة فقط.
هذا التصميم مقبول إذا كان النظام يعرف طريقة دفع واحدة ولن يتغير. لكن في كود العمل، طرق الدفع تتغير كثيراً. المشكلة هنا ليست في CardPayment نفسها؛ المشكلة أن checkout تأخذ نوعاً محدداً. أي طريقة جديدة ستحتاج إما دالة جديدة أو شروطاً داخل الدالة الحالية.
الخطوة 2: استخرج السلوك
السلوك المهم هو Pay(amount float64) string. إذن نعرّفه كواجهة.
لاحظ أن الواجهة صغيرة جداً. هذا مقصود. الواجهة التي تحتوي method واحدة سهلة الفهم وسهلة التطبيق. لا تضف Refund أو Validate أو Name قبل أن تحتاجها الدالة المستهلكة فعلاً. في Go القاعدة العملية هي: ابدأ بالشيء الذي يحتاجه المستهلك الآن، واترك التوسع حتى يظهر احتياج حقيقي.
الخطوة 3: أضف نوعاً جديداً دون تغيير checkout
هذه هي النقطة المهمة: نضيف تحويل بنكي بدون تعديل دالة checkout.
هنا تظهر قيمة التصميم. دالة checkout لا تعرف هل الدفع بطاقة أو تحويل. هي تعرف فقط أن لديها شيئاً يملك Pay. هذا يقلل coupling بين الأجزاء. لو أردت لاحقاً إضافة ApplePay أو CashPayment فلن تحتاج تعديل checkout ما دام النوع الجديد يطبق نفس method.
متى لا تستخدم interface؟
لا تستخدم الواجهة فقط لأنها تبدو “احترافية”. إذا كان لديك نوع واحد ولا توجد دالة تحتاج قبول أكثر من تنفيذ، فالنوع المباشر أوضح. الواجهة تصبح مفيدة عندما توجد حدود: كود عال المستوى يريد الاعتماد على سلوك، وكود منخفض المستوى يوفر التنفيذ. في مثال الدفع، checkout يمثل الحد العالي، وطرق الدفع تمثل التفاصيل.
من الأخطاء الشائعة أيضاً وضع الواجهة بجانب كل نوع تنفيذي. الأفضل غالباً أن تعرّف الواجهة قرب المكان الذي يستهلكها، لأن المستهلك هو من يعرف أصغر سلوك يحتاجه. لو عرّفت واجهة ضخمة من جهة طريقة الدفع، ستجبر كل الأنواع على تنفيذ أشياء لا تستخدمها.
تحدي موجه
أضف طريقة دفع نقدية CashPayment واجعلها تعمل مع نفس دالة checkout.
فحص الحل
الحل الصحيح لا يغير توقيع checkout. إذا وجدت نفسك تكتب checkoutCash أو تضيف if يفحص نوع الدفع، فأنت لم تستفد من الواجهة. المطلوب أن تضيف type جديداً وتكتب له method باسم Pay وبنفس التوقيع. بعدها تستطيع تمريره إلى checkout مثل البطاقة تماماً.
تذكر أن الواجهة في Go عقد صغير بين أجزاء البرنامج. عندما يكون العقد صغيراً وواضحاً، يصبح استبدال التنفيذ سهلاً. هذا هو الأساس الذي ستراه لاحقاً في اختبارات تستخدم fake repository، أو كود HTTP يقبل logger مختلفاً، أو خدمات تدفع إلى مزود خارجي دون أن تربط كل النظام بهذا المزود.