Go

[Design Pattern] 커맨드(Command), 템플릿(Template), 메멘토(Memento) 패턴

구루싸 2022. 11. 29. 14:19
반응형
SMALL

커맨드(Command)

커맨드(Command) 디자인 패턴은 전략 설계 패턴과 매우 유사하지만 주요 차이점이 있습니다. 전략 패턴에서는 알고리즘 변경에 초점을 맞추는 반면, 커맨드 패턴에서는 무언가의 호출 또는 어떤 유형의 추상화에 초점을 맞춥니다.

package main

import "fmt"

type Command interface {
	Execute()
}

type ConsoleOutput struct {
	message string
}

func (c *ConsoleOutput) Execute() {
	fmt.Println(c.message)
}

func CreateCommand(s string) Command {
	fmt.Println("Creating command")

	return &ConsoleOutput{
		message: s,
	}
}

type CommandQueue struct {
	queue []Command
}

func (p *CommandQueue) AddCommand(c Command) {
	p.queue = append(p.queue, c)

	if len(p.queue) == 3 {
		for _, command := range p.queue {
			command.Execute()
		}
		p.queue = make([]Command, 3)
	}
}

func main() {
	queue := CommandQueue{}

	queue.AddCommand(CreateCommand("First message"))
	queue.AddCommand(CreateCommand("Second message"))
	queue.AddCommand(CreateCommand("Third message"))
	queue.AddCommand(CreateCommand("Fourth message"))
	queue.AddCommand(CreateCommand("Fifth message"))
}

템플릿(Template)

템플릿 디자인 패턴은 특히 라이브러리와 프레임워크를 작성할 때 매우 유용하게 사용되는 패턴 중 하나입니다. 이 아이디어는 사용자에게 알고리즘 내에서 코드를 실행할 수 있는 방법을 제공하는 것입니다.

package template

import "strings"

type MessageRetriever interface {
	Message() string
}

type Template interface {
	first() string
	third() string
	ExecuteAlgorithm(MessageRetriever) string
}

type TemplateImpl struct{}

func (t *TemplateImpl) first() string {
	return "hello"
}

func (t *TemplateImpl) third() string {
	return "template"
}

func (t *TemplateImpl) ExecuteAlgorithm(m MessageRetriever) string {
	return strings.Join([]string{t.first(), m.Message(), t.third()}, " ")
}

type adapter struct {
	myFunc func() string
}

func (a *adapter) Message() string {
	if a.myFunc != nil {
		return a.myFunc()
	}
	return ""
}

func MessageRetrieverAdapter(f func() string) MessageRetriever {
	return &adapter{myFunc: f}
}

Test Code

package template

import (
	"strings"
	"testing"
)

type TestStruct struct {
	Template
}

func (m *TestStruct) Message() string {
	return "world"
}

func TestTemplate_ExecuteAlgorithm(t *testing.T) {
	t.Run("Using interfaces", func(t *testing.T) {
		s := &TestStruct{}
		template := &TemplateImpl{}
		res := template.ExecuteAlgorithm(s)
		expected := "world"
		if !strings.Contains(res, expected) {
			t.Errorf("Expected string '%s' wasn't found on returned string\n",
				expected)
		}
	})
	t.Run("Using anonymous functions adapted to an interface", func(t *testing.T) {
		messageRetriever := MessageRetrieverAdapter(func() string {
			return "world"
		})
		if messageRetriever == nil {
			t.Fatal("Can not continue with a nil MessageRetriever")
		}
		template := &TemplateImpl{}
		res := template.ExecuteAlgorithm(messageRetriever)
		expectedOrError(res, " world ", t)
	})
}

func expectedOrError(res string, expected string, t *testing.T) {
	if !strings.Contains(res, expected) {
		t.Errorf("Expected string '%s' was not found on returned string\n",
			expected)
	}
}

메멘토(Memento)

메멘토 디자인 패턴은 기본적으로 일부 상태가 포함된 유형을 사용하여 상태의 마일스톤을 저장할 수 있습니다. 저장된 상태의 양이 한정되어 있으므로 작업 취소, 기록 등 다양한 작업에 필요한 경우 복구할 수 있습니다.

package memento

import "fmt"

type State struct {
	Description string
}

type memento struct {
	state State
}

type careTaker struct {
	mementoList []memento
}

func (c *careTaker) Add(m memento) {
	c.mementoList = append(c.mementoList, m)
}

func (c *careTaker) Memento(i int) (memento, error) {
	if len(c.mementoList) < i || i < 0 {
		return memento{}, fmt.Errorf("index not found")
	}
	return c.mementoList[i], nil
}

type originator struct {
	state State
}

func (o *originator) NewMemento() memento {
	return memento{state: o.state}
}
func (o *originator) ExtractAndStoreState(m memento) {
	o.state = m.state
}

Test Code

package memento

import "testing"

func TestCareTaker_Add(t *testing.T) {
	originator := originator{}
	originator.state = State{Description: "Idle"}
	careTaker := careTaker{}
	mem := originator.NewMemento()
	if mem.state.Description != "Idle" {
		t.Error("Expected state was not found")
	}

	currentLen := len(careTaker.mementoList)
	careTaker.Add(mem)
	if len(careTaker.mementoList) != currentLen+1 {
		t.Error("No new elements were added on the list")
	}
}

func TestCareTaker_Memento(t *testing.T) {
	originator := originator{}
	careTaker := careTaker{}
	originator.state = State{"Idle"}
	careTaker.Add(originator.NewMemento())
	mem, err := careTaker.Memento(0)
	if err != nil {
		t.Fatal(err)
	}
	if mem.state.Description != "Idle" {
		t.Error("Unexpected state")
	}
	mem, err = careTaker.Memento(-1)
	if err == nil {
		t.Fatal("An error is expected when asking for a negative number but no error was found")
	}
}

func TestOriginator_ExtractAndStoreState(t *testing.T) {
	originator := originator{state: State{"Idle"}}
	idleMemento := originator.NewMemento()
	originator.ExtractAndStoreState(idleMemento)
	if originator.state.Description != "Idle" {
		t.Error("Unexpected state found")
	}
}

 

반응형
LIST