Menu
Coddy logo textTech

Komut Kalıbı

Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 91 / 107.

Komut deseni (Command pattern), bir isteği bir nesne olarak kapsüller; bu da işlemleri parametreleştirmenize, kuyruğa almanıza veya geri alma işlevini desteklemenize olanak tanır. Strateji (Strategy) algoritmaları kapsüllerken, Komut tüm eylemleri parametreleriyle birlikte kapsüller.

Go'da, bir Execute metoduna sahip bir Command arayüzü tanımlarız, ardından bir eylemi gerçekleştirmek için gereken tüm bilgileri tutan somut komutlar oluştururuz:

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"
}

Bir çağırıcı (invoker), komutların ne yaptığını bilmeden onları saklar ve yürütür:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

Çağırıcı (invoker), alıcıdan (Işık) tamamen ayrılmıştır (decoupled):

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Işık açıldı

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Işık kapatıldı

Command; işlemleri depolamanız, geciktirmeniz veya yeniden oynatmanız gereken geri al/yinele sistemleri, görev kuyrukları veya makro kaydetme özelliklerini uygulamak için idealdir.

challenge icon

Görev

Kolay

Command desenini kullanarak geri alma (undo) özelliğine sahip bir metin düzenleyici oluşturalım! Metin işlemlerini kapsülleyen komutlar oluşturacaksınız; bu, düzenleyicinin eylemleri yürütmesine ve geri almasına olanak tanır—bu, komutların neden bu kadar güçlü olduğunu gösteren klasik bir kullanım örneğidir.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • command.go: Komut arayüzünüzü ve somut metin düzenleme komutlarını tanımlayın.

    Eylemi gerçekleştirmek için Execute() string ve eylemi geri almak için Undo() string olmak üzere iki yöntemi olan bir Command arayüzü oluşturun.

    Bir *TextEditor üzerinde çalışan iki komut türü uygulayın:

    • InsertCommand — düzenleyici işaretçisini ve eklenecek metni tutar. Execute, metni düzenleyicinin içeriğine ekler ve Inserted: [text] döndürür. Undo, bu metni sondan kaldırır ve Undone insert: [text] döndürür.
    • DeleteCommand — düzenleyici işaretçisini ve sondan silinecek karakter sayısını tutar. Execute, bu karakterleri kaldırır (geri alma için saklar) ve Deleted: [removed text] döndürür. Undo, bunları geri yükler ve Undone delete: [restored text] döndürür.
  • editor.go: Metin düzenleyicinizi (alıcı/receiver) ve komut geçmişini yöneten çağırıcıyı (invoker) oluşturun.

    Bir Content alanına (string) ve Append(text string) ile DeleteLast(count int) string (silinen metni döndürür) yöntemlerine sahip bir TextEditor yapısı (struct) oluşturun.

    Yürütülen komutların bir dilimini (slice) geçmiş olarak tutan bir EditorInvoker yapısı oluşturun. Şu yöntemleri ekleyin:

    • ExecuteCommand(cmd Command) string — komutu yürütür, geçmişe ekler ve sonucu döndürür.
    • UndoLast() string — son komutu geçmişten kaldırır, onun Undo yöntemini çağırır ve sonucu döndürür. Geçmiş boşsa, Nothing to undo döndürür.
  • main.go: Komut sisteminizi bir dizi işlemle gösterin.

    İşlem sayısını okuyun. Her işlem için işlem türünü (insert, delete veya undo) okuyun. insert için eklenecek metni de okuyun. delete için karakter sayısını okuyun. Her işlemi çağırıcınız (invoker) aracılığıyla yürütün ve sonucu yazdırın. Tüm işlemlerden sonra, nihai düzenleyici içeriğini Final: [content] olarak yazdırın.

Aşağıdaki girdiler sağlanacaktır:

  • Satır 1: İşlem sayısı
  • Her işlem için: işlem türü, ardından gerekirse ek veriler (insert için metin, delete için sayı)

Örneğin, şu girdi verildiğinde:

5
insert
Hello
insert
 World
delete
3
undo
undo

Çıktınız şu şekilde olmalıdır:

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

Ve şu girdi verildiğinde:

4
insert
Go
insert
Lang
undo
insert
!

Çıktınız şu şekilde olmalıdır:

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

Ve şu girdi verildiğinde:

2
undo
insert
Test

Çıktınız şu şekilde olmalıdır:

Nothing to undo
Inserted: Test
Final: Test

Her komutun, eylemini hem gerçekleştirmek hem de geri almak için gereken her şeyi nasıl kapsüllediğine dikkat edin. Çağırıcı (invoker), komutların ne yaptığını bilmez; sadece onları yürütür ve geri alma desteği için geçmişi tutar!

Kopya kağıdı

Command deseni, bir isteği bir nesne olarak kapsülleyerek işlemleri parametreleştirmenize, kuyruğa almanıza veya geri alma (undo) işlevini desteklemenize olanak tanır.

Bir Execute metoduna sahip bir Command arayüzü tanımlayın:

type Command interface {
    Execute() string
}

Bir eylemi gerçekleştirmek için gereken tüm bilgileri tutan somut komutlar oluşturun:

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"
}

Bir çağırıcı (invoker), komutların ne yaptığını bilmeden onları saklar ve yürütür:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

Çağırıcı ve alıcı arasındaki ayrımı (decoupling) gösteren kullanım örneği:

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Işık açıldı

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Işık kapatıldı

Geri alma (undo) işlevi için, Command arayüzüne bir Undo metodu ekleyin ve çağırıcıda komut geçmişini tutun.

Kendin dene

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// İşlem sayısını oku
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Editor ve invoker'ı oluştur
	editor := &TextEditor{}
	invoker := &EditorInvoker{}
	
	for i := 0; i < numOps; i++ {
		// İşlem türünü oku
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// Eklenecek metni oku
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: Bir InsertCommand oluştur ve bunu invoker aracılığıyla çalıştır
			_ = text
			_ = editor
			
		case "delete":
			// Silinecek karakter sayısını oku
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Bir DeleteCommand oluştur ve bunu invoker aracılığıyla çalıştır
			_ = count
			
		case "undo":
			// TODO: invoker üzerinde UndoLast metodunu çağır
			
		}
		
		fmt.Println(result)
	}
	
	// Final içeriği yazdır
	fmt.Printf("Final: %s\n", editor.Content)
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler