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 ๊ตฌ๋ฌธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Expression
  • CommandListExpression: ์—ฌ๋Ÿฌ๊ฐœ์˜ 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)๋กœ์„œ ํŠน์ • ์–ธ์–ด์˜ ๋ฌธ์žฅ์„ ํ‘œํ˜„ํ•˜๊ณ ์ž ํ• ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค.
    • ์ •์˜ํ•  ์–ธ์–ด์˜ ๋ฌธ๋ฒ•์ด ๊ฐ„๋‹จํ•  ๊ฒฝ์šฐ
    • ํšจ์œจ์„ฑ์„ ๊ณ ๋ คํ•  ํ•„์š”๊ฐ€ ์—†์„ ๊ฒฝ์šฐ

๊ฒฐ๊ณผ

  • ์žฅ์ 
    1. ๋ฌธ๋ฒ•์˜ ๋ณ€๊ฒฝ๊ณผ ํ™•์žฅ์ด ์‰ฝ๋‹ค.
    2. ๋ฌธ๋ฒ•์˜ ๊ตฌํ˜„์ด ์šฉ์ดํ•˜๋‹ค
  • ๋‹จ์ 
    1. ๋ณต์žกํ•œ ๋ฌธ๋ฒ•์€ ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

์ƒ๊ฐํ•ด๋ณผ ์ 

  • ํŒŒ์„œ์— ๊ตญํ•œ๋˜์–ด ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์•„๋ณด์ด๋Š” ํŒจํ„ด์ด๋‹ค.
  • ์ด๊ฑธ ํŒจํ„ด์ด๋ผ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋„ ์•ฝ๊ฐ„ ์˜๋ฌธ์ด ๋“ ๋‹ค.
  • 17. Command ํŒจํ„ด์˜ ์‘์šฉ์ด๋ผ๊ณ  ๋ณด๋Š” ๊ฒƒ์ด ๋” ์ข‹์„ ๋“ฏ
  • ํŒŒ์„œ์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋ฉด ํ•œ๋ฒˆ์ฏค ์ƒ๊ฐํ•ด ๋ณผ๋งŒ ํ•˜๋‹ค.
  • ์ปดํŒŒ์ผ๋Ÿฌ ๊ตฌํ˜„์— ๋„๋ฆฌ ์‚ฌ์šฉ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

Reference