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

نمط الأمر

جزء من قسم البرمجة كائنية التوجه في رحلة 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 مثاليًا لتنفيذ أنظمة التراجع/الإعادة، أو قوائم انتظار المهام، أو تسجيل وحدات الماكرو عندما تحتاج إلى تخزين العمليات أو تأخيرها أو إعادة تشغيلها.

challenge icon

التحدي

سهل

لنَبْنِ محرّر نصوص مع وظيفة التراجع باستخدام نمط 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)
}
quiz iconاختبر نفسك

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

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

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