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

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

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

يحدد نمط Template Method الهيكل العام لخوارزمية في نوع أساسي، مما يسمح للأنواع الفرعية بتجاوز خطوات محددة دون تغيير الهيكل العام. بينما يضيف 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 في العمل!

ورقة مرجعية

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

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

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 }

تقوم طريقة القالب بتنسيق الخطوات بترتيب ثابت:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // Saved: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Stored: parsed-json-data

يعد نمط طريقة القالب (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