Menu
CoddyTech

نمط الأمر

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

يغلّف نمط الأمر الطلب في كائن، مما يتيح لك تحديد العمليات كمعلمات، أو وضعها في قائمة انتظار، أو دعم وظيفة التراجع. وبينما يغلّف 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 := &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: عرّف واجهة الأوامر وأوامر تحرير النصوص الملموسة.

    أنشئ واجهة Command تتضمن طريقتين: Execute() string لتنفيذ الإجراء وUndo() string لعكسه.

    نفّذ نوعَي أوامر يعملان على *TextEditor:

    • InsertCommand: يحتفظ بمؤشر المحرر والنص المراد إدراجه. تُلحِق Execute النص بمحتوى المحرر وتُرجع Inserted: [text]. تزيل Undo ذلك النص من النهاية وتُرجع Undone insert: [text]
    • DeleteCommand: يحتفظ بمؤشر المحرر وعدد الأحرف المراد حذفها من النهاية. تزيل Execute تلك الأحرف (وتخزّنها للتراجع) وتُرجع Deleted: [removed text]. تستعيدها Undo وتُرجع Undone delete: [restored text]
  • editor.go: أنشئ محرر النصوص (المستقبِل) والمُنفِّذ الذي يدير سجل الأوامر.

    أنشئ بنية TextEditor بحقل Content (من النوع string) وطريقتين هما Append(text string) وDeleteLast(count int) string (تُرجع النص المحذوف).

    أنشئ بنية EditorInvoker تحتفظ بشريحة من الأوامر المنفّذة كسجل. أضف الطريقتين التاليتين:

    • ExecuteCommand(cmd Command) string: تنفّذ الأمر، وتضيفه إلى السجل، وتُرجع النتيجة
    • UndoLast() string: تزيل الأمر الأخير من السجل، وتستدعي Undo الخاص به، وتُرجع النتيجة. إذا كان السجل فارغًا، فأرجع Nothing to undo
  • main.go: اعرض نظام الأوامر بسلسلة من العمليات.

    اقرأ عدد العمليات. ولكل عملية، اقرأ نوع العملية (insert أو delete أو undo). بالنسبة إلى insert، اقرأ أيضًا النص المراد إدراجه. وبالنسبة إلى delete، اقرأ عدد الأحرف. نفّذ كل عملية عبر المُنفِّذ واطبع النتيجة. بعد جميع العمليات، اطبع محتوى المحرر النهائي بصيغة 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

لاحظ كيف يغلّف كل أمر كل ما يلزم لتنفيذ الإجراء وعكسه. لا يعرف المُنفِّذ ما تفعله الأوامر؛ فهو ينفّذها فحسب ويحافظ على السجل لدعم التراجع!

جرّب بنفسك

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 عبر الإنترنت