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.
Görev
KolayCommand 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() stringve eylemi geri almak içinUndo() stringolmak üzere iki yöntemi olan birCommandarayü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 veInserted: [text]döndürür. Undo, bu metni sondan kaldırır veUndone 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) veDeleted: [removed text]döndürür. Undo, bunları geri yükler veUndone 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
Contentalanına (string) veAppend(text string)ileDeleteLast(count int) string(silinen metni döndürür) yöntemlerine sahip birTextEditoryapısı (struct) oluşturun.Yürütülen komutların bir dilimini (slice) geçmiş olarak tutan bir
EditorInvokeryapı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 undodö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,deleteveyaundo) 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ğiniFinal: [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: HelloVe ş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: TestHer 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)
}
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
1Go OOP Temelleri
Harici DosyalarGo Çalışma Alanı ve ModüllerPaketler ve İçe AktarmalarDışa Aktarılan ve Aktarılmayan İsimlerGo'da OOP'ye GirişSınıf Olarak Struct'larStruct'larda Metot TanımlamaPointer ve Değer AlıcılarStruct BaşlatmaYapıcı FonksiyonlarÖzet - Basit Hesap Makinesi4Arayüzler
Arayüzlere GirişÖrtük UygulamaSözleşme Olarak ArayüzBoş Arayüz (any)Tür OnaylamaTür SeçimiArayüz BileşimiStringer ve Error ArayüzleriÖzet - Şekil Hesaplayıcı7Kapsülleme
Dışa Aktarılan ve Aktarılmayan AlanlarPaket Seviyesinde KapsüllemeGetter ve Setter MetotlarıGo'da Bilgi GizlemeÖzet - Öğrenci Kayıtları10Generics (Go 1.18+)
Generics'e GirişTür ParametreleriTür KısıtlamalarıGeneric Struct'larGeneric Metotlar İçin Geçici ÇözümÖzet - Generic Koleksiyon13Tasarım Kalıpları Bölüm 1
Tasarım Kalıplarına GirişSingleton KalıbıFactory KalıbıAbstract Factory KalıbıObserver KalıbıStrategy Kalıbı2Türler ve Struct'lar: Derinlemesine İnceleme
Temel ve Bileşik TürlerÖzel Tür TanımlamalarıStruct EtiketleriAnonim Struct'larİç İçe Geçmiş Struct'larSıfır Değerler ve VarsayılanlarÖzet - Rehber5Kalıtım Yerine Kompozisyon
Go'da Neden Kalıtım YokStruct Gömme TemelleriMetot YükseltmeBirden Fazla Struct GömmeGömme vs AgregasyonGömülü Metotları GölgelemeÖzet - Çalışan Hiyerarşisi8Hata Yönetimi ve OOP
error ArayüzüÖzel Hata TürleriHata Sarmalama (fmt.Errorf)Sentinel Hatalarerrors.Is() ve errors.As()Panic, Defer ve RecoverÖzet - Dosya Ayrıştırıcı11Standart Kütüphane ve OOP
io.Reader ve io.Writersort.Interface Arayüzüfmt.Stringer ArayüzüStruct'lar ile encoding/jsonhttp.Handler ArayüzüÖzet - REST API Modelleri14Tasarım Kalıpları Bölüm 2
Komut KalıbıAdaptör KalıbıDekoratör KalıbıŞablon Metot KalıbıDurum KalıbıKompozit KalıbıDekoratör olarak Middleware3İşaretçiler ve Bellek
Go'da İşaretçi TemelleriStruct İşaretçileriDeğer ile Geçme ve Referans ile Geçmenew() FonksiyonuGo'da Garbage CollectionÖzet - Linked List Oluşturucu6Go'da Polimorfizm
Interface'ler ile PolimorfizmGo'da Duck TypingInterface Karşılama KurallarıPolimorfik KoleksiyonlarDependency InjectionÖzet - Payment Processor9Eşzamanlılık ve OOP
Goroutine TemelleriKanallar ve İletişimTamponlu ve Tamponsuz KanallarSelect İfadesisync.Mutex ve sync.RWMutexsync.WaitGroupThread-Safe Struct TasarımıÖzet - Worker Pool