Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط طريقة القالب

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 94 من 107.

يحدد نمط طريقة القالب (Template Method pattern) الهيكل العظمي لخوارزمية ما في نوع أساسي، مما يسمح للأنواع الفرعية بتجاوز خطوات محددة دون تغيير الهيكل العام. بينما يضيف نمط "المزخرف" (Decorator) سلوكاً عن طريق تغليف الكائنات، فإن "طريقة القالب" (Template Method) تتحكم في تدفق الخوارزمية وتسمح بتخصيص الخطوات الفردية.

في لغة Go، وبما أنه لا يوجد وراثة، فإننا نقوم بتنفيذ هذا النمط باستخدام تضمين الهياكل (struct embedding) مع الواجهات (interfaces). يحدد هيكل الـ "template" بنية الخوارزمية ويستدعي الدوال التي يمكن تخصيصها:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

توفر التنفيذات الملموسة إصداراتها الخاصة من كل خطوة بينما يظل تدفق الخوارزمية دون تغيير:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

تُنظم طريقة القالب Execute الخطوات بترتيب ثابت:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // تم الحفظ: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // تم التخزين: parsed-json-data

يُعد نمط القالب (Template Method) مثاليًا عندما يكون لديك خوارزميات تتشارك في نفس الهيكل ولكنها تختلف في خطوات محددة، مثل خطوط أنابيب استيراد/تصدير البيانات، أو مولدات التقارير، أو أطر عمل الاختبار.

challenge icon

التحدي

سهل

لنقم ببناء نظام لإنشاء التقارير باستخدام نمط Template Method! ستقوم بإنشاء إطار عمل حيث تتبع أنواع التقارير المختلفة نفس عملية الإنشاء — جمع البيانات، تنسيقها، وإخراج النتيجة — ولكن كل نوع تقرير يخصص هذه الخطوات بشكل مختلف.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • report.go: حدد الواجهة (interface) والمعالج الأساسي الذي ينظم خوارزمية إنشاء التقارير.

    قم بإنشاء واجهة ReportGenerator مع ثلاث طرق (methods) تمثل خطوات إنشاء التقرير:

    • GatherData() string — يسترجع البيانات الخام للتقرير
    • FormatData(data string) string — يحول البيانات إلى تنسيق التقرير
    • OutputReport(formatted string) string — ينتج رسالة المخرجات النهائية

    قم ببناء هيكل ReportProcessor يحتوي على تنفيذ لـ ReportGenerator. أضف طريقة Generate() string تقوم بتنفيذ الخطوات الثلاث بالترتيب: الجمع، التنسيق، ثم الإخراج — مع إرجاع النتيجة النهائية.

  • generators.go: قم بتنفيذ مولدات تقارير ملموسة تخصص كل خطوة.

    أنشئ نوعين من التقارير:

    • SalesReport مع حقل Region (string)
      • GatherData() يرجع "sales-data-[region]"
      • FormatData(data) يرجع "SALES REPORT: [data]"
      • OutputReport(formatted) يرجع "Printed: [formatted]"
    • InventoryReport مع حقل Warehouse (string)
      • GatherData() يرجع "inventory-[warehouse]"
      • FormatData(data) يرجع "*** [data] ***"
      • OutputReport(formatted) يرجع "Exported: [formatted]"
  • main.go: وضح كيف ينتج نفس هيكل الخوارزمية نتائج مختلفة بناءً على التنفيذ.

    اقرأ نوع التقرير ("sales" أو "inventory") وقيمة التكوين (المنطقة للمبيعات، أو المستودع للمخزون). قم بإنشاء المولد المناسب، ولفه في ReportProcessor، واستدعِ Generate()، واطبع النتيجة.

سيتم توفير المدخلات التالية:

  • السطر 1: نوع التقرير ("sales" أو "inventory")
  • السطر 2: قيمة التكوين (اسم المنطقة أو اسم المستودع)

على سبيل المثال، إذا كان المدخل:

sales
North

يجب أن يكون مخرجك:

Printed: SALES REPORT: sales-data-North

وإذا كان المدخل:

inventory
MainHub

يجب أن يكون مخرجك:

Exported: *** inventory-MainHub ***

وإذا كان المدخل:

sales
West

يجب أن يكون مخرجك:

Printed: SALES REPORT: sales-data-West

لاحظ كيف يقوم ReportProcessor دائمًا باستدعاء نفس الخطوات الثلاث بنفس الترتيب، ولكن كل نوع تقرير يقدم تنفيذه الخاص لتلك الخطوات. يظل هيكل الخوارزمية ثابتًا بينما تختلف التفاصيل — هذا هو نمط Template Method قيد التنفيذ!

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// قراءة نوع التقرير
	scanner.Scan()
	reportType := scanner.Text()
	
	// قراءة قيمة التكوين (المنطقة أو المستودع)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: إنشاء المولد المناسب بناءً على reportType
	// - إذا كان reportType هو "sales"، فقم بإنشاء SalesReport مع تعيين Region إلى configValue
	// - إذا كان reportType هو "inventory"، فقم بإنشاء InventoryReport مع تعيين Warehouse إلى configValue
	
	// TODO: إنشاء ReportProcessor باستخدام المولد
	
	// TODO: استدعاء Generate() وطباعة النتيجة
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming