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

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

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 94 من 107.

يُعرِّف نمط Template Method الهيكل الأساسي لـ algorithm في نوع أساسي، مما يتيح للأنواع الفرعية تجاوز خطوات محددة دون تغيير البنية العامة. بينما يضيف Decorator سلوكًا من خلال تغليف الكائنات، يتحكم Template Method في تدفق algorithm ويسمح بتخصيص الخطوات الفردية.

في Go، بما أنه لا توجد وراثة، نُنفّذ هذا النمط باستخدام تضمين البنى مع الواجهات. تحدد البنية "template" بنية الخوارزمية وتستدعي methods التي يمكن تخصيصها:

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: Define الواجهة الخاصة بك وBaseProcessor الذي ينسق algorithm إنشاء التقرير.

    أنشئ واجهة ReportGenerator تحتوي على ثلاثة methods تمثل خطوات إنشاء التقرير:

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

    أنشئ بنية ReportProcessor التي holds تطبيقًا لـ ReportGenerator. أضف method باسم Generate() string executes الخطوات الثلاث بالترتيب: الجمع، ثم التنسيق، ثم الإخراج، مع إرجاع النتيجة النهائية.

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

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

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

    اقرأ نوع التقرير (sales أو inventory) وقيمة configuration (المنطقة للمبيعات، أو المستودع للمخزون). أنشئ generator المناسب، وغلفه داخل ReportProcessor، واستدعِ Generate()، ثم اطبع النتيجة.

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

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

على سبيل المثال، عند إعطاء:

sales
North

ينبغي أن يكون إخراجك:

Printed: SALES REPORT: sales-data-North

وعند إعطاء:

inventory
MainHub

ينبغي أن يكون إخراجك:

Exported: *** inventory-MainHub ***

وعند إعطاء:

sales
West

ينبغي أن يكون إخراجك:

Printed: SALES REPORT: sales-data-West

لاحظ أن ReportProcessor يستدعي دائمًا الخطوات الثلاث نفسها بالترتيب نفسه، لكن كل نوع من التقارير يوفر تطبيقه الخاص لهذه الخطوات. يظل هيكل algorithm ثابتًا بينما تختلف التفاصيل: ذلك هو نمط 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اختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت