هل تساءلت يوماً لماذا تفشل بعض المشاريع البرمجية رغم أنها تعمل في البداية؟ أو لماذا يصبح تعديل كود معين كابوساً بعد أشهر من كتابته؟ الإجابة غالباً تكمن في هندسة البرمجيات وليس في الكود نفسه. في هذا المقال، سنتعمق في نمط التصميم المعياري (Modular Component Pattern – MCP) ونوضح كيف استخدمناه لبناء مشروع حقيقي مفتوح المصدر: Dawen AI – أداة كتابة مقالات SEO بالذكاء الاصطناعي.
ما هو نمط التصميم المعياري (Modular Component Pattern)؟
نمط التصميم المعياري هو أسلوب هندسي لتنظيم الكود البرمجي بحيث يتم تقسيم النظام إلى وحدات مستقلة (Modules)، كل وحدة منها:
- مسؤولة عن مهمة واحدة فقط (Single Responsibility)
- قابلة للاستبدال دون التأثير على باقي النظام
- قابلة للاختبار بشكل منفصل
- قابلة للتوسع بإضافة وحدات جديدة
تشبيه بسيط: فكر في الأمر كقطع الليغو (LEGO) كل قطعة لها شكل ووظيفة محددة، ويمكنك تركيبها مع قطع أخرى لبناء أي شيء تريده. إذا أردت تغيير لون قطعة معينة، لا تحتاج لإعادة بناء كل شيء.
لماذا يجب أن تستخدم النمط المعياري في مشاريعك؟
1. سهولة الصيانة (Maintainability)
عندما يكون كل جزء من الكود في مكانه المحدد، يصبح إيجاد وإصلاح الأخطاء أسهل بكثير. لن تضطر للبحث في آلاف الأسطر للوصول لمشكلة بسيطة.
2. إعادة الاستخدام (Reusability)
الوحدات المعيارية يمكن نقلها بين المشاريع. مثلاً، وحدة “البحث في الإنترنت” التي بنيناها يمكن استخدامها في أي مشروع آخر يحتاجها.
3. العمل الجماعي (Team Collaboration)
في الفرق البرمجية، يمكن لكل مطور العمل على وحدة مختلفة دون التأثير على عمل الآخرين.
4. الاختبار (Testing)
يمكنك كتابة اختبارات (Unit Tests) لكل وحدة بشكل منفصل، مما يضمن جودة عالية للكود.
دراسة حالة عملية: مشروع Dawen AI
لنرى كيف طبقنا هذا المفهوم عملياً في مشروع Dawen AI، وهو أداة تولد مقالات SEO باستخدام الذكاء الاصطناعي.
هيكل المشروع
ai-seo-writer/
├── app/
│ ├── input/ # وحدة معالجة المدخلات
│ │ └── handler.py
│ ├── research/ # وحدة البحث في الإنترنت
│ │ └── engine.py
│ ├── planner/ # وحدة التخطيط (العقل المدبر)
│ │ └── planner.py
│ ├── ai/ # وحدة التجريد للذكاء الاصطناعي
│ │ └── provider.py
│ ├── seo/ # وحدة كتابة المحتوى
│ │ └── writer.py
│ └── exporter/ # وحدة التصدير
│ └── file_exporter.py
├── config/
│ └── settings.py
├── main.py # نقطة الدخول الرئيسية
└── requirements.txt
تدفق البيانات بين الوحدات
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Keyword │ ──▶ │ Input │ ──▶ │ Research │
│ (مدخل) │ │ Handler │ │ Engine │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ File │ ◀── │ SEO │ ◀── │ Content │
│ Export │ │ Writer │ │ Planner │
└─────────────┘ └─────────────┘ └─────────────┘
شرح تفصيلي لكل وحدة مع الكود
الوحدة 1: معالج المدخلات (Input Handler)
المسؤولية: تنظيف الكلمة المفتاحية والتحقق من صحتها.
# app/input/handler.py
class InputHandler:
@staticmethod
def cleanup_keyword(keyword: str) -> str:
"""تنظيف الكلمة المفتاحية من المسافات الزائدة"""
if not keyword:
return ""
return " ".join(keyword.strip().split())
@staticmethod
def validate_keyword(keyword: str) -> bool:
"""التحقق من أن الكلمة المفتاحية صالحة"""
cleaned = InputHandler.cleanup_keyword(keyword)
if not cleaned:
raise ValueError("الكلمة المفتاحية لا يمكن أن تكون فارغة")
word_count = len(cleaned.split())
if word_count > 6:
raise ValueError(f"الكلمة المفتاحية طويلة جداً ({word_count} كلمات)")
return True
@staticmethod
def process(keyword: str) -> str:
"""نقطة الدخول الرئيسية للوحدة"""
cleaned = InputHandler.cleanup_keyword(keyword)
InputHandler.validate_keyword(cleaned)
return cleaned
لماذا هذا التصميم؟
- الوحدة لا تعرف شيئاً عن البحث أو الكتابة
- يمكن اختبارها بشكل منفصل
- يمكن استبدالها بوحدة أخرى دون تغيير باقي الكود
الوحدة 2: طبقة التجريد للذكاء الاصطناعي (AI Provider Abstraction)
المسؤولية: عزل مزود الذكاء الاصطناعي عن باقي النظام.
⚠️ هذه الوحدة هي قلب النمط المعياري! لأنها تسمح لنا بتغيير مزود AI (مثلاً من Gemini إلى OpenAI) بتعديل سطر واحد فقط.
# app/ai/provider.py
from abc import ABC, abstractmethod
import google.generativeai as genai
class BaseAIProvider(ABC):
"""الواجهة الأساسية - كل مزود يجب أن يطبق هذه الدالة"""
@abstractmethod
def generate_content(self, prompt: str) -> str:
pass
class GeminiProvider(BaseAIProvider):
"""تطبيق Gemini للواجهة الأساسية"""
def __init__(self):
genai.configure(api_key=Config.GEMINI_API_KEY)
self.model = genai.GenerativeModel('gemini-2.5-flash')
def generate_content(self, prompt: str) -> str:
response = self.model.generate_content(prompt)
return response.text
# ========================================
# إضافة مزود جديد (مثال: OpenAI)
# ========================================
# class OpenAIProvider(BaseAIProvider):
# def __init__(self):
# self.client = OpenAI(api_key=Config.OPENAI_KEY)
#
# def generate_content(self, prompt: str) -> str:
# response = self.client.chat.completions.create(...)
# return response.choices[0].message.content
def get_provider() -> BaseAIProvider:
"""مصنع لإرجاع المزود المطلوب"""
return GeminiProvider()
# أو: return OpenAIProvider()
الفائدة العملية:
- تريد الانتقال من Gemini إلى OpenAI؟ فقط غيّر سطر واحد في
get_provider() - باقي الكود لا يعرف ولا يهتم بأي مزود تستخدم
- هذا يُسمى Dependency Inversion Principle من مبادئ SOLID
الوحدة 3: محرك البحث (Research Engine)
المسؤولية: البحث في الإنترنت وجمع المعلومات.
# app/research/engine.py
from duckduckgo_search import DDGS
class ResearchEngine:
def __init__(self, max_results=5):
self.max_results = max_results
def search(self, query: str) -> dict:
"""البحث وإرجاع النتائج بصيغة موحدة"""
results = []
try:
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=self.max_results):
results.append({
"title": r.get('title', ''),
"href": r.get('href', ''),
"body": r.get('body', '')
})
except Exception as e:
print(f"خطأ في البحث: {e}")
# نفشل بأمان بدلاً من تعطيل النظام
pass
return {
"sources": [r['href'] for r in results],
"snippets": [r['body'] for r in results],
"titles": [r['title'] for r in results]
}
نقطة مهمة: Fail Gracefully
لاحظ كيف نتعامل مع الأخطاء. إذا فشل البحث، لا يتعطل النظام بالكامل. هذا مبدأ أساسي في التصميم المعياري.
الوحدة 4: مخطط المحتوى (Content Planner)
المسؤولية: تحويل نتائج البحث إلى خطة مقال منظمة.
# app/planner/planner.py
class ContentPlanner:
def __init__(self, ai_provider: BaseAIProvider):
self.ai = ai_provider # نستقبل المزود كـ Dependency
def create_plan(self, keyword: str, research_data: dict) -> dict:
snippets = "\n\n".join(research_data.get('snippets', []))
prompt = f"""
أنت خبير استراتيجية محتوى SEO.
الكلمة المفتاحية: "{keyword}"
السياق من نتائج البحث:
{snippets}
المطلوب: أنشئ خطة مقال تفصيلية بصيغة JSON تتضمن:
- main_topic: عنوان H1 جذاب
- search_intent: نية البحث
- outline: هيكل المقال (H2, H3)
- secondary_keywords: كلمات مفتاحية ثانوية
"""
response = self.ai.generate_content(prompt)
return self._parse_json(response)
لاحظ: الوحدة لا تكتب المقال! فقط تخطط. هذا مثال على Single Responsibility.
الوحدة 5: كاتب SEO (SEO Writer)
المسؤولية: كتابة المقال الفعلي بناءً على الخطة.
# app/seo/writer.py
class SEOWriter:
def __init__(self, ai_provider: BaseAIProvider):
self.ai = ai_provider
def write_article(self, plan: dict, research_data: dict) -> str:
prompt = f"""
أنت كاتب محتوى SEO محترف.
# الخطة المطلوب تنفيذها
{json.dumps(plan, ensure_ascii=False, indent=2)}
# المتطلبات
- استخدم Markdown (H1, H2, H3)
- اكتب 1000+ كلمة
- وزع الكلمات المفتاحية بشكل طبيعي
- ابدأ مباشرة بالعنوان
"""
return self.ai.generate_content(prompt)
الوحدة 6: مصدّر الملفات (File Exporter)
المسؤولية: حفظ النتيجة النهائية في ملف.
# app/exporter/file_exporter.py
class FileExporter:
def __init__(self, output_dir="output"):
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
def save_article(self, keyword: str, content: str) -> str:
slug = self._slugify(keyword)
date_str = datetime.now().strftime("%Y-%m-%d")
filename = f"{slug}-{date_str}.md"
filepath = os.path.join(self.output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
return filepath
نقطة الدخول: main.py – التنسيق بين الوحدات
الآن نرى كيف تتصل كل هذه الوحدات معاً:
# main.py
from app.input.handler import InputHandler
from app.ai.provider import get_provider
from app.research.engine import ResearchEngine
from app.planner.planner import ContentPlanner
from app.seo.writer import SEOWriter
from app.exporter.file_exporter import FileExporter
def main():
keyword = input("أدخل الكلمة المفتاحية: ")
# الخطوة 1: معالجة المدخلات
clean_keyword = InputHandler.process(keyword)
# الخطوة 2: تهيئة الوحدات
ai = get_provider()
researcher = ResearchEngine()
planner = ContentPlanner(ai)
writer = SEOWriter(ai)
exporter = FileExporter()
# الخطوة 3: تنفيذ السلسلة
research_data = researcher.search(clean_keyword)
plan = planner.create_plan(clean_keyword, research_data)
article = writer.write_article(plan, research_data)
# الخطوة 4: التصدير
filepath = exporter.save_article(clean_keyword, article)
print(f"تم حفظ المقال في: {filepath}")
if __name__ == "__main__":
main()
لاحظ:
main.pyفقط “ينسق” بين الوحدات- لا يحتوي على منطق أعمال (Business Logic)
- كل وحدة تفعل شيئاً واحداً فقط
كيف تبني نظامك الخاص؟ (خطوات عملية)
الخطوة 1: حدد المهام الأساسية
قبل كتابة أي كود، اسأل نفسك: ما هي المهام المنفصلة التي يحتاجها النظام؟
مثال لمشروع تحليل بيانات:
- قراءة البيانات من مصادر مختلفة
- تنظيف البيانات
- تحليل البيانات
- تصدير التقارير
الخطوة 2: ارسم هيكل المجلدات
my-project/
├── app/
│ ├── data_loader/
│ ├── cleaner/
│ ├── analyzer/
│ └── reporter/
├── config/
└── main.py
الخطوة 3: حدد الواجهات (Interfaces)
لكل وحدة، حدد:
- ما هي المدخلات؟
- ما هي المخرجات؟
- ما هي الأخطاء المحتملة؟
الخطوة 4: ابنِ وحدة واحدة في كل مرة
ابدأ بالوحدة الأبسط، اختبرها، ثم انتقل للتالية.
الخطوة 5: اربط الوحدات في main.py
اجعل نقطة الدخول بسيطة قدر الإمكان.
الأخطاء الشائعة (وكيف تتجنبها)
❌ خطأ 1: الوحدة العملاقة (God Module)
وضع كل الكود في ملف واحد.
الحل: إذا تجاوز الملف 200 سطر، فكر في تقسيمه.
❌ خطأ 2: الاعتماد المباشر (Direct Dependencies)
# خطأ ❌
class Writer:
def __init__(self):
self.ai = GeminiProvider() # مرتبط بـ Gemini مباشرة
الحل: Dependency Injection
# صحيح ✅
class Writer:
def __init__(self, ai_provider: BaseAIProvider):
self.ai = ai_provider # يستقبل أي مزود
❌ خطأ 3: المعلومات المتسربة (Leaky Abstraction)
عندما تكشف الوحدة تفاصيل داخلية لا يحتاجها المستخدم.
الحل: اجعل الواجهة (Interface) بسيطة قدر الإمكان.
الخلاصة
نمط التصميم المعياري (Modular Component Pattern) ليس مجرد طريقة لتنظيم الملفات، بل هو فلسفة تفكير تجعل الكود:
- أسهل في الفهم والقراءة
- أسهل في الصيانة والتطوير
- أسهل في الاختبار
- قابل للتوسع بدون إعادة كتابة
مشروع Dawen AI هو مثال حي على هذا النمط. يمكنك استكشاف الكود الكامل على GitHub:
🔗 github.com/ahmedalisoliman2030-a11y/dawen_ai
هل أنت مستعد للبدء؟
ابدأ مشروعك القادم بالتفكير في الوحدات أولاً، وليس في الكود. ستلاحظ الفرق الكبير في جودة العمل وسهولة التطوير.
شاركنا في التعليقات: ما هو المشروع الذي تفكر في بنائه باستخدام النمط المعياري؟

