نمط طريقة القالب
جزء من قسم البرمجة كائنية التوجه في رحلة 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 مثالية عندما تكون لديك خوارزميات تشترك في البنية نفسها، لكنها تختلف في خطوات محددة، مثل مسارات استيراد/تصدير البيانات، أو مولدات التقارير، أو أطر الاختبار.
التحدي
سهللنبنِ نظامًا لإنشاء التقارير باستخدام نمط Template Method! ستنشئ إطارًا تتبع فيه أنواع التقارير المختلفة عملية الإنشاء نفسها، إذ تجمع البيانات وتنسقها وتُخرج النتيجة، لكن كل نوع من التقارير يخصص هذه الخطوات بطريقة مختلفة.
ستنظم الشيفرة في ثلاثة ملفات:
report.go: Define الواجهة الخاصة بك وBaseProcessor الذي ينسق algorithm إنشاء التقرير.أنشئ واجهة
ReportGeneratorتحتوي على ثلاثة methods تمثل خطوات إنشاء التقرير:GatherData() string: تسترجع البيانات الخام للتقريرFormatData(data string) string: تحول البيانات إلى تنسيق التقريرOutputReport(formatted string) string: تنتج رسالة الإخراج النهائية
أنشئ بنية
ReportProcessorالتي holds تطبيقًا لـReportGenerator. أضف method باسمGenerate() stringexecutes الخطوات الثلاث بالترتيب: الجمع، ثم التنسيق، ثم الإخراج، مع إرجاع النتيجة النهائية.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")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Poolتدرّب بنفسك: مترجم Go عبر الإنترنت