GoF์ ๋์์ธ ํจํด, ํด์์ ํจํด์ ๋ํด ์์๋ณธ๋ค.
ํด๋น ๊ธ์, ๋ค์์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ดํดํ๋ ๊ฒ์ด ํธ๋ฆฌํฉ๋๋ค.
ํต์ฌ ์์ฝ
- ๋ฌธ๋ฒ์ ๋ง์ถฐ ์์ฑ๋ ์คํฌ๋ฆฝํธ๋ฅผ ํด์
- ํด์๋ ๊ตฌ๋ฌธ์ ์ ํด์ง ๊ท์น๋๋ก ์คํํ๋ ํจํด
์์
Script: BEGIN FRONT LOOP 2 BACK RIGHT END BACK END
์ฝ๊ฒ ๋ณด๊ธฐ
BEGIN // ์คํฌ๋ฆฝํธ ์์
FRONT // [๋ช
๋ น] ์์ผ๋ก ๊ฐ๊ธฐ
LOOP 2 // ๋ฐ๋ณต๋ฌธ ์์, ๋ฐ๋ณต ํ์
BACK // [๋ช
๋ น] ๋ค๋ก ๊ฐ๊ธฐ
RIGHT // [๋ช
๋ น] ์ค๋ฅธ์ชฝ์ผ๋ก ๊ฐ๊ธฐ
END // ์คํฌ๋ฆฝํธ ๋
BACK // [๋ช
๋ น] ๋ค๋ก ๊ฐ๊ธฐ
END // ์คํฌ๋ฆฝํธ ๋
- Expression: BEGIN FRONT LOOP 2 BACK RIGHT END BACK END ์์ ํํ๋ ๊ฐ ํ ๋จ์ด
- Context: Expression์ ์ ํด์ง ๋ฌธ๋ฒ์ ๋ง์ถฐ ํด์๋ ๊ฒฐ๊ณผ
Context
: ์คํฌ๋ฆฝํธ์์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ดExpression
: ์คํฌ๋ฆฝํธ๋ฅผ ๊ตฌ์ฑํ๋ ๊ฐ ๊ตฌ๋ฌธ์ ์ฒ๋ฆฌBeginExpression
:BEGIN
๊ตฌ๋ฌธ์ ์ฒ๋ฆฌํ๋ ExpressionCommandListExpression
: ์ฌ๋ฌ๊ฐ์CommandExpression
์ ๊ฐ์ง ์ ์์CommandExpression
: ์ค์ ์คํํ ์ ์๋ ๋ช ๋ น์ ๋ํ ๊ตฌ๋ฌธ (LOOP
,BACK
etc)์ ๋ํ๋ด๋ ์ธํฐํ์ด์คLoopCommandExpression
: ๋ฐ๋ณต๋ฌธ ๋ฃจํ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ตฌ๋ฌธActionCommandExpression
:FRONT
,BACK
,RIGHT
,LEFT
์ ๋์์ ์ฒ๋ฆฌํ๋ ๊ตฌ๋ฌธ
Code
main
import Foundation
internal func main() {
let script = "BEGIN FRONT LOOP 2 BACK RIGHT END BACK LOOP 4 BACK FRONT LEFT END LEFT END"
let context = Context(script: script)
let expression = BeginExpression()
if expression.parse(context: context) {
print(expression.description)
}
}
main()
- script๋ฅผ ๋ฐ์ context์ ๋ฃ์ด ์ด๋ฅผ ๋ถ์ํ๋ค.
- ์ค์ ํ์์ ๊ธฐ๋ฅ์ด ๊ตฌ๋ถ๋ expression์ ์ด context๋ฅผ ํ๊ณ ํ๊ณ ๋๊ธฐ๋ฉด์ ๋ถ์์ ์งํํ๋ค.
- ์์์ BeginExpression์ด ๋ ๊ฒ์ด๋ฏ๋ก, ์ฌ๊ธฐ์ ๋ถ์๋ Context๋ฅผ ๋ฃ์ด ํ์ฑ ๊ธฐ๋ฅ์ ๋์์ํจ๋ค.
Context
import Foundation
internal class Context {
private(set) var currentKeyword: String?
internal init(script: String) {
self.tokenizer = Tokenizer(script: script)
self.readNextKeyword()
}
internal func readNextKeyword() {
self.currentKeyword = self.tokenizer.nextToken
}
private let tokenizer: Tokenizer
}
internal class Tokenizer {
internal init(script: String) {
self.tokens = script.components(separatedBy: .whitespaces)
}
internal var nextToken: String? {
guard self.tokens.isEmpty == false else {
return nil
}
return self.tokens.removeFirst()
}
private var tokens: [String]
}
Context
๋ ์ค์ ๋ฌธ์์ด์ ํ ํฐ์ผ๋ก ๋๋ ์ฃผ๋ ํ ํฌ๋์ด์ ๋ฅผ ๊ฐ๋๋ค.Context
๋ ์ธ๋ถ์์ ์ฝ๊ฒ ๋ค์ ํ ํฐ์ ์ป๊ธฐ ์ํ Wrapping ํด๋์ค๋ผ ์๊ฐํ๋ฉด ๋๊ฒ ๋ค.
Expression
import Foundation
internal protocol Expression: Loggable {
func parse(context: Context) -> Bool
func run() -> Bool
}
internal protocol KeywordAcceptable {
static func isValid(keyword: String) -> Bool
}
internal protocol Loggable {
var description: String { get }
}
- ์ค์ ํ์์ ๊ธฐ๋ฅ์ด ๋ด๊ธธ ์ธํฐํ์ด์ค์ด๋ค.
- ๊ธฐ๋ฅ์ด ๋ด๊ฒจ์๊ธฐ ๋๋ฌธ์ Command ํจํด์ ์ผ์ข ์ด๋ผ ๋ณด์๋ ๋ฌด๋ฐฉํ๋ค.
parse
๋Context
๋ฅผ ๋ฐ์ ์์ ์ด ์ฒ๋ฆฌํ ์ ์๋์ง ํ์ธํ๊ณ ,- ๊ทธ์ ๋ง๋ ํ์ expression์ ๋ง๋๋ ์ฑ ์์ ๊ฐ๋๋ค.
run
์ ๋ง๋ค์ด์ง ๋ค์ expression๋ค์ ๋ํด ๋์์ ์คํํ๊ณ ์ ํํ๋ ์ญํ ์ ํ๋ค.KeywordAcceptable
์ ํ์ ๊ธฐ๋ฅ์ค์ ๊ตฌ๋ฌธ๊ณผ ์ฆ๊ฐ ๋์๋๋ Expression์ ๋ํด ์ด๋ฅผ ์ ์ํด์ฃผ๊ธฐ ์ํด ๋ง๋ค์๋ค.
BeginExpression
import Foundation
internal class BeginExpression: Expression {
internal func parse(context: Context) -> Bool {
// ๋ด ํค์๋๊ฐ ๋ง๋์ง ํ์ธ
guard let keyword = context.currentKeyword,
Self.isValid(keyword: keyword) else {
return false
}
// ํ์ Expression ์์ฑ
self.expression = CommandListExpression()
// ๋ค์์ผ๋ก ๋๊ธฐ๊ธฐ ์ Context ํ์ฒ๋ฆฌ
context.readNextKeyword()
guard let expression else {
return false
}
return expression.parse(context: context);
}
internal func run() -> Bool {
guard let expression else {
return false
}
return expression.run()
}
private var expression: CommandListExpression?
}
extension BeginExpression: KeywordAcceptable {
internal static func isValid(keyword: String) -> Bool {
keyword == "BEGIN"
}
}
extension BeginExpression: Loggable {
internal var description: String {
"BEGIN " + "[" + (self.expression?.description ?? "") + "]"
}
}
CommandListExpression
import Foundation
internal class CommandListExpression: Expression {
internal func parse(context: Context) -> Bool {
var result: Bool = true
while true {
guard let keyword = context.currentKeyword else {
result = false
break
}
guard keyword != "END" else {
context.readNextKeyword()
break
}
guard let command = self.determineCommand(with: keyword),
command.parse(context: context) else {
result = false
break
}
self.commands.append(command)
}
return result
}
internal func run() -> Bool {
for command in self.commands {
guard command.run() else {
return false
}
}
return true
}
// ์๋๋ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ํ๋๊ฒ ์ข์๋ฐ ๊ทธ๋ฅ ๋์ถฉ ํจ
private func determineCommand(with keyword: String) -> CommandExpression? {
let command: CommandExpression?
if LoopCommandExpression.isValid(keyword: keyword) {
command = LoopCommandExpression(keyword: keyword)
} else if ActionCommandExpression.isValid(keyword: keyword) {
command = ActionCommandExpression(keyword: keyword)
} else {
command = nil
}
return command
}
private var commands = [CommandExpression]()
}
extension CommandListExpression: Loggable {
internal var description: String {
self.commands.map { $0.description }.joined(separator: " ")
}
}
CommandExpression
import Foundation
internal protocol CommandExpression: Expression, KeywordAcceptable {
var keyword: String { get }
}
- Command์ ๊ฒฝ์ฐ์๋ ํญ์ ํค์๋์ ๋์๋๋ ๊ธฐ๋ฅ์ ๊ฐ์ง ์ ๋ฐ์ ์๋ค.
- ๊ทธ๋ ๊ธฐ์ ํญ์ ํค์๋๋ฅผ ๊ฐ์ง ์ ์๋๋ก ํ๋ค.
LoopCommandExpression
import Foundation
internal class LoopCommandExpression: CommandExpression {
internal let keyword: String
internal var count: Int?
internal init(keyword: String) {
self.keyword = keyword
}
internal func parse(context: Context) -> Bool {
guard Self.isValid(keyword: self.keyword) else {
return false
}
context.readNextKeyword()
guard let count = context.currentKeyword else {
return false
}
self.count = Int(count)
context.readNextKeyword()
guard context.currentKeyword != nil else {
return false
}
self.expression = CommandListExpression()
guard let expression else {
return false
}
return expression.parse(context: context)
}
internal func run() -> Bool {
guard let count, let expression else {
return false
}
for _ in (0..<count) {
guard expression.run() else {
return false
}
}
return true
}
private var expression: CommandListExpression?
}
extension LoopCommandExpression: KeywordAcceptable {
internal static func isValid(keyword: String) -> Bool {
keyword == "LOOP"
}
}
extension LoopCommandExpression: Loggable {
internal var description: String {
"LOOP(\(self.count ?? 0))" + "{" + (self.expression?.description ?? "") + "}"
}
}
ActionCommandExpression
import Foundation
internal class ActionCommandExpression: CommandExpression {
internal let keyword: String
internal init(keyword: String) {
self.keyword = keyword
}
internal func parse(context: Context) -> Bool {
guard Self.isValid(keyword: self.keyword) else {
return false
}
context.readNextKeyword()
guard context.currentKeyword != nil else {
return false
}
return true
}
internal func run() -> Bool {
print("cmd: \(self.keyword)")
return true
}
}
extension ActionCommandExpression: KeywordAcceptable {
internal static func isValid(keyword: String) -> Bool {
["FRONT", "BACK", "LEFT", "RIGHT"].contains(keyword)
}
}
extension ActionCommandExpression: Loggable {
internal var description: String {
self.keyword
}
}
๊ฒฐ๊ณผ
BEGIN [FRONT LOOP(2){BACK RIGHT} BACK LOOP(4){BACK FRONT LEFT} LEFT]
cmd: FRONT
cmd: BACK
cmd: RIGHT
cmd: BACK
cmd: RIGHT
cmd: BACK
cmd: BACK
cmd: FRONT
cmd: LEFT
cmd: BACK
cmd: FRONT
cmd: LEFT
cmd: BACK
cmd: FRONT
cmd: LEFT
cmd: BACK
cmd: FRONT
cmd: LEFT
cmd: LEFT
ํ์ฉ์ฑ
- ADT(Abstract Syntax Tree)๋ก์ ํน์ ์ธ์ด์ ๋ฌธ์ฅ์ ํํํ๊ณ ์ ํ ๋ ์ฌ์ฉํ๋ฉด ์ข๋ค.
- ์ ์ํ ์ธ์ด์ ๋ฌธ๋ฒ์ด ๊ฐ๋จํ ๊ฒฝ์ฐ
- ํจ์จ์ฑ์ ๊ณ ๋ คํ ํ์๊ฐ ์์ ๊ฒฝ์ฐ
๊ฒฐ๊ณผ
- ์ฅ์
- ๋ฌธ๋ฒ์ ๋ณ๊ฒฝ๊ณผ ํ์ฅ์ด ์ฝ๋ค.
- ๋ฌธ๋ฒ์ ๊ตฌํ์ด ์ฉ์ดํ๋ค
- ๋จ์
- ๋ณต์กํ ๋ฌธ๋ฒ์ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ต๋ค.
์๊ฐํด๋ณผ ์
- ํ์์ ๊ตญํ๋์ด ์ฌ์ฉํ๊ธฐ ์ข์๋ณด์ด๋ ํจํด์ด๋ค.
- ์ด๊ฑธ ํจํด์ด๋ผ ํ ์ ์๋์ง๋ ์ฝ๊ฐ ์๋ฌธ์ด ๋ ๋ค.
- 17. Command ํจํด์ ์์ฉ์ด๋ผ๊ณ ๋ณด๋ ๊ฒ์ด ๋ ์ข์ ๋ฏ
- ํ์์ ๊ฐ์ ์ญํ ์ ํ๋ ๋ฌด์ธ๊ฐ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค๋ฉด ํ๋ฒ์ฏค ์๊ฐํด ๋ณผ๋ง ํ๋ค.
- ์ปดํ์ผ๋ฌ ๊ตฌํ์ ๋๋ฆฌ ์ฌ์ฉ๋๋ค๊ณ ํ๋ค.