일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- go 마스코트
- 좀비고루틴
- 골랑 고퍼
- go air
- go air 환경변수
- go
- 신입개발자
- go channel
- go clean architecture
- 개발자
- gin recovery
- go panic
- go 캐릭터
- air 환경변수
- golang gopher
- go 대기그룹
- go 패닉
- go디자인패턴
- go 맥 air 환경변수
- gin logger
- clean architecture middleware
- 고루틴 채널
- gin middleware
- go recover
- go middleware
- go 환경변수
- go 맥
- go 맥 air
- gopath 환경변수
- git
- Today
- Total
뽀미의 개발노트
Go 디자인패턴 - Behavioral Patterns (행동 패턴) 본문
코드 참고 링크
https://github.com/gobenpark/go-design-pattern
GitHub - gobenpark/go-design-pattern
Contribute to gobenpark/go-design-pattern development by creating an account on GitHub.
github.com
패턴 설명 참고 링크
https://refactoring.guru/ko/design-patterns/what-is-pattern
디자인 패턴이란?
디자인 패턴이란? 디자인 패턴은 소프트웨어 디자인 과정에서 자주 발생하는 문제들에 대한 전형적인 해결책입니다. 이는 코드에서 반복되는 디자인 문제들을 해결하기 위해 맞춤화할 수 있는
refactoring.guru
행동패턴 1) Strategy Pattern
행동(알고리즘)을 동적으로 선택할 수 있게 해주는 방식. 특정 기능을 ‘실행하는 방식’에서만 차이가 있는 유사한 클래스가 있는 경우 사용함. 집에서 회사까지 가는 방법이 지하철/자동차/걷기 이런식으로 여러 전략이 있는 것처럼. 코드에서 조건문을 최소화하고 유연성과 확장성을 높일 수 있음.
var output = flag.String("output", "console", "The output to use between 'console' and 'image' file")
func main() {
flag.Parse()
var activeStrategy OutputStrategy
switch *output {
case "console":
activeStrategy = &TextSquare{}
case "image":
activeStrategy = &ImageSquare{"/tmp/image.jpg"}
default:
activeStrategy = &TextSquare{}
}
err := activeStrategy.Draw()
if err != nil {
log.Fatal(err)
}
}
OutputStrategy 인터페이스의 Draw() 메서드를 TextSquare와 ImageSquare 구조체에서 따로 구현함. 사용자 입력 (“console” 또는 “image”) 에 따라 적합한 전략을 선택하여 알맞은 동작을 행하도록 함. 각 전략을 캡슐화 해놓고 실행 시점(런타임)에 유연하게 전환하여 적합한 전략을 선택할 수 있도록 함. 새로운 전략을 추가할 때 기존 코드에 영향 주지 않고 새로운 타입만 구현하면 됨.
전략 패턴의 장단점
장점 : if-else 구문 혹은 switch 문으로 행동을 결정하지 않고, 전략을 ‘객체’로 분리하여 동작을 런타임에 동적으로 변경 가능. 각 전략은 고유한 동작을 담당하므로 코드가 명확하고 유지보수가 용이함.
단점 : 어차피 사용자의 입력값에 따라 if-else, switch문으로 동적으로 전략을 택하도록 해야함. 전략이 많아질수록 각각의 방식에 대해 별도 클래스를 만들어야함. 그러면 코드가 너무 복잡해짐.
행동패턴 2) Chain Responsibility Pattern
핸들러들의 체인을 따라 요청을 전달할 수 있게 해주는 디자인 패턴. 각 핸들러는 요청을 자신이 처리할지 아니면 다음 핸들러로 전달할지를 결정함. 특정 순서로 여러 핸들러를 실행해야 할 때 적합.
책임 연쇄 패턴의 장단점
장점 : 요청 처리 로직을 독립적인 체인 노드로 분리할 수 있어 코드 확장과 수정에 용이함. 각 체인 노드는 하나의 처리 책임만 가져 단일 책임 원칙(Single Responsibility Principle)을 잘 지킴.
단점 : 체인이 너무 길어지면 성능 저하되거나 디버깅 어려움. 구현 잘못 하면 요청 끝나지 않고 체인 계속 순환함.
책임 연쇄 패턴 적용 사례
로깅 시스템 : 각 로깅 레벨(정보, 경고, 오류)을 독립적인 체인으로 구성함.
이벤트 처리 : GUI 이벤트의 처리 (버튼 클릭 → 상위 컨테이너로 전달)
Class diagram
행동패턴 3) Command Pattern
요청을 객체로 캡슐화하여 실행 시점을 결정할 수 있도록 하는 패턴. 명령 객체를 생성하여 큐에 저장하고, 일정 조건에 따라 큐의 명령을 실행하여 명령의 요청과 실행을 분리할 수 있음. 클라이언트는 구체적인 실행 방식에 대해서 알 필요 없이 Command의 Execute 만 호출하면 됨.
커맨드 패턴 예시
작업의 실행을 ‘예약’해야할 때 : 커맨드를 직렬화하여 대기열에 추가하거나 로깅하거나 전송할 수 있음.
되돌릴 수 있는 작업 구현할 때 : 잘라내기 및 붙여넣기 등의 커맨드에 적합. 커맨드 실행 전 상태의 백업 복사본 만듦. 커맨드 실행되면 커맨드랑 백업 복사본이랑 같이 커맨드 기록(커맨드 객체들 스택)에 배치함. 나중에 사용자가 Ctrl+Z 하면 기록에서 가장 최근 커맨드를 가져와 백업본으로 되돌릴 수 있음
Class diagram
행동패턴 4) Template Pattern
알고리즘의 뼈대를 정의하고, 세부 사항은 서브클래스에서 구현할 수 있게 해주는 패턴.
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
type People []Person
// Len 메서드 (사람의 수)
func (p People) Len() int {
return len(p)
}
// Swap 메서드 (두 사람의 위치를 바꾸기)
func (p People) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
// Less 메서드 (나이 기준으로 정렬)
func (p People) Less(i, j int) bool {
// 나이가 더 적은 사람을 먼저 배치
return p[i].Age < p[j].Age
}
func main() {
people := People{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20},
}
fmt.Println("Before sort:", people)
// 정렬 (나이 순으로)
sort.Sort(people)
fmt.Println("After sort:", people)
}
Go에는 sort.Interface가 내장되어 있음. sort 인터페이스 안에는 Len(), Swap(), Less() 알고리즘 뼈대가 고정되어 있음. 근데 그 메서드들을 사용자가 커스터마이징할 수 있음. 어떻게 구현하느냐에 따라 정렬 방식이 달라짐. 그 후에는 sort.Sort() 함수만 호출하면 내가 정의한 규칙에 따라 정렬됨. 위와 같이 세 메서드를 커스텀 하여 Person 타입을 Age에 따라 정렬하도록 할 수 있음. sort.Sort(people)만 호출하면 Less 메서드에 사용자가 정의한 대로 나이가 적은 사람이 먼저 오도록 정렬됨.
Class diagram
행동패턴 5) Memento Pattern
객체의 이전 상태를 저장했다가 복원할 수 있게 해주는 패턴. 캡쳐(스냅샷)하듯이. Undo/Redo 기능 또는 게임 상태 복원 등에서 사용됨. 객체의 상태를 안전하게 복원할 수 있고 상태 변경 이력을 관리할 수 있음.
Originator
state를 저장하고 복원하려는 객체. 상태를 변경하고 그 상태를 memento 객체로 캡슐화하여 저장하거나 복원할 수 있음. SetState()로 상태를 변경하고 CreateMemento()로 현재 state를 Memento 객체에 저장함.
Memento
state를 저장하는 객체. 이 객체는 Originator의 내부 state를 캡슐화하여 가지고 있으며 외부에서 접근할 수 없도록 함.
Caretaker
Memento를 저장하고 관리하는 객체. 메멘토를 저장하고 디비에 저장할건지/ 몇개까지 저장할건지 등등에 로직을 가짐. AddMemento()로 상태를 저장하고 RestoreMemento()로 이전 상태를 복원.(Undo/Redo)
커맨드 패턴과 메멘토 패턴 비교
둘다 되돌리기 작업에 사용되기 좋은데 각기 다른 방식으로 작동함.
커맨드 패턴 : ‘명령’ 자체를 객체로 만들어 캡슐화하고, 요청과 실행을 분리하는데 초점을 맞추는 패턴임. 사용자가 수행한 작업을 되돌릴 때 사용함. 복사/잘라내기/붙여넣기와 같은 ‘기능’을 실행하거나 되돌릴 경우 적합함.
메멘토 패턴 : 객체의 ‘상태’를 저장하고 복원하는 패턴임. 작업이 아니라 상태를 되돌려야 하는 경우 사용함. 문서 내용이나 게임의 상태와 같은 것을 복원해야 할 경우 적합.
Class diagram
행동패턴 6) Visitor Pattern
객체 구조체를 수정하지 않고 새로운 연산 추가하는 패턴. MessageA와 MessageB라는 메세지를 저장하고 출력하는 구조체가 원래 있었음. 여기에 새로운 기능(텍스트 추가하는 기능)을 추가하고 싶은데 원래 구조체를 건드리고 싶지 않음. 그래서 Visitor 인터페이스의 Visit() 메서드를 통해 기존 구조체의 기능을 확장하도록 함. MessageVisitor 구조체가 Visitor 인터페이스를 구현하고, 기존 구조체를 방문하여 각각 수행할 구체적인 동작을 정의함.
비지터 패턴의 장단점
장점 : 기존 구조체의 구조를 바꾸지 않고 새로운 기능 추가 가능(안정적). 데이터 구조체와 연산을 분리하여 유지보수 용이함.
단점 : 비지터가 많아질 경우 코드가 복잡해지고 어느 비지터가 어떤 기능을 수행하는지 파악 어려움. 만약 기존 구조체를 변경하면 모든 비지터를 수정해야 할 수 있음. 매번 비지터를 호출해야 하면 성능 저하 발생 가능.
Class diagram
행동패턴 7) State Pattern
상태를 따로 구조체로 만들지 않을 경우 : 각 구조체에서 상태에 따라 적절한 행동을 하도록 if~else 문이나 switch문으로 구현해야함. 그런데 이렇게 하면 각 구조체마다 거대한 조건문을 모든 메서드에 전부 포함해야하고 코드 유지보수 어려움. 따라서 상태 패턴을 사용하여 상태를 객체화하고 상태와 관련된 작업을 그 객체에 위임해야함. state에 따라 다르게 행동하는 객체가 있거나, state가 많거나, state별로 코드가 자주 변경될 때 사용하기 적합. 상태는 객체의 현 상황을 나타내는 것이기 때문에 ‘유일’해야 하고, 따라서 싱글톤으로 구성되어야 함.
전략 패턴(Strategy pattern)과 상태 패턴(State pattern)의 차이
상태패턴에서 state들은 서로를 인식하고 한 상태에서 다른 상태로 transition할 수 있으나, 전략패턴에서 state들은 서로에 대해 알지 못함. 전략패턴은 전략 알고리즘을 구조체로 표현한 패턴이지만, 상태 패턴은 상태 그 자체를 ‘객체’로 표현함.
Class diagram
'아키텍처' 카테고리의 다른 글
Go 클린 아키텍처 (2) | 2024.12.19 |
---|---|
Go 디자인패턴 - Concurrency Patterns (동시성 패턴) (0) | 2024.12.19 |
Go 디자인패턴 - Structural Patterns (구조 패턴) (0) | 2024.12.19 |
Go 디자인패턴 - Creational Patterns (생성패턴) (0) | 2024.12.19 |