نمط الأمر
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 91 من 107.
يُغلّف نمط Command الطلبَ في هيئة كائن، مما يتيح لك تحديد العمليات بالمعلمات، أو وضعها في قائمة انتظار، أو دعم وظيفة التراجع. وبينما يُغلّف Strategy الخوارزميات، يُغلّف Command الإجراءات الكاملة مع معلماتها.
في Go، نُعرّف واجهة Command باستخدام أسلوب Execute، ثم ننشئ أوامر ملموسة تحتفظ بكل المعلومات اللازمة لتنفيذ إجراء:
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}يخزّن المستدعي الأوامر وينفّذها دون معرفة ما تفعله:
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}يكون المُستدعي منفصلًا تمامًا عن المُستقبِل (المصباح):
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // تم تشغيل الضوء
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // تم إطفاء الضوءيُعد Command مثاليًا لتنفيذ أنظمة التراجع/الإعادة، أو قوائم انتظار المهام، أو تسجيل وحدات الماكرو عندما تحتاج إلى تخزين العمليات أو تأخيرها أو إعادة تشغيلها.
التحدي
سهللنَبْنِ محرّر نصوص مع وظيفة التراجع باستخدام نمط Command! ستنشئ أوامر تُغلّف عمليات النص، مما يتيح للمحرّر تنفيذ الإجراءات وعكسها: إنها حالة استخدام كلاسيكية توضّح سبب قوة الأوامر.
ستنظّم شيفرتك عبر ثلاثة ملفات:
command.go: Define واجهة command والأوامر الملموسة لتحرير النصوص.أنشئ واجهة
Commandمع طريقتين:Execute() stringلتنفيذ الإجراء وUndo() stringلعكسه.Implement نوعين من الأوامر يعملان على
*TextEditor:InsertCommand: holds مؤشر المحرّر والنص المراد إدراجه. ينفّذ Execute عملية Append للنص إلى محتوى المحرّر ويعيدInserted: [text]. يزيل Undo ذلك النص من النهاية ويعيدUndone insert: [text]DeleteCommand: holds مؤشر المحرّر وعدد characters المراد حذفها من النهاية. يزيل Execute تلك characters (مع تخزينها من أجل Undo) ويعيدDeleted: [removed text]. يستعيد Undo تلك characters ويعيدUndone delete: [restored text]
editor.go: أنشئ محرّر النصوص (receiver) وinvoker الذي يدير history الأوامر.أنشئ بنية
TextEditorمع الحقلContent(من النوع string) وطريقتين هماAppend(text string)وDeleteLast(count int) string(تعيد النص المحذوف).أنشئ بنية
EditorInvokerالتي holds شريحة من الأوامر المنفّذة باعتبارها history. أضف هذه الطرق:ExecuteCommand(cmd Command) string: تنفّذ الأمر، وتضيفه إلى history، وتعيد النتيجةUndoLast() string: تزيل آخر أمر من history، وتستدعي Undo الخاص به، وتعيد النتيجة. إذا كانت history فارغة، فأعدNothing to undo
main.go: اعرض نظام command الخاص بك باستخدام سلسلة من العمليات.اقرأ عدد العمليات. لكل عملية، اقرأ نوع العملية (
insertأوdeleteأوundo). بالنسبة إلى insert، اقرأ أيضًا النص المراد إدراجه. وبالنسبة إلى delete، اقرأ عدد characters. نفّذ كل عملية عبر invoker الخاص بك واطبع النتيجة. بعد جميع العمليات، اطبع محتوى المحرّر النهائي بالشكلFinal: [content].
سيتم توفير المدخلات التالية:
- السطر 1: عدد العمليات
- لكل عملية: نوع العملية، ثم البيانات الإضافية عند الحاجة (النص من أجل insert، والعدد من أجل delete)
على سبيل المثال، عند إعطاء:
5
insert
Hello
insert
World
delete
3
undo
undoيجب أن يكون الناتج:
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: Helloوعند إعطاء:
4
insert
Go
insert
Lang
undo
insert
!يجب أن يكون الناتج:
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!وعند إعطاء:
2
undo
insert
Testيجب أن يكون الناتج:
Nothing to undo
Inserted: Test
Final: Testلاحظ كيف يغلّف كل أمر كل ما يلزم لتنفيذ action وعكسه. لا يعرف invoker ما تفعله الأوامر. إنه ينفّذها فقط ويحافظ على history لدعم التراجع!
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة عدد العمليات
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// إنشاء المحرر والمستدعي
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// قراءة نوع العملية
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// قراءة النص المراد إدراجه
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: أنشئ InsertCommand ونفّذه من خلال المستدعي
_ = text
_ = editor
case "delete":
// قراءة عدد الأحرف المراد حذفها
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: أنشئ DeleteCommand ونفّذه من خلال المستدعي
_ = count
case "undo":
// TODO: استدعِ UndoLast على المستدعي
}
fmt.Println(result)
}
// طباعة المحتوى النهائي
fmt.Printf("Final: %s\n", editor.Content)
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت