Today I Learned

23년 7월 25일 TIL

jjjkh 2023. 7. 25. 23:35

프로토콜을 이용하여 클래스 혹은 구조체로 채택하여 if문, switch문 guard문을 반복적으로 사용하지 않고 프로그래밍을 할 수 있다.

이런 프로그래밍을 절차지향이라고 하는데 절차 지향으로 프로그래밍을 하다보면 코드가 복잡해지고 유지보수가 힘들게 코드가 꼬일수있다.

그래서 이번에 사용해본 객체지향 프로그래밍인데 프로토콜을 사용하여 구조체,클래스에 채택하여 간결하고 보수가 쉽게 만들 수 있었다.

 

아래 메뉴판을 보시고 메뉴를 골라 입력해주세요.

[ SHAKESHACK MENU ]
1. Burgers         | 앵거스 비프 통살을 다져만든 버거
2. Frozen Custard  | 매장에서 신선하게 만드는 아이스크림
3. Drinks          | 매장에서 직접 만드는 음료
4. Beer            | 뉴욕 브루클린 브루어리에서 양조한 맥주
0. 종료            | 프로그램 종료
1 <-

[ Burgers MENU ]
1. ShackBurger   | W 6.9 | 토마토, 양상추, 쉑소스가 토핑된 치즈버거
2. SmokeShack    | W 8.9 | 베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거
3. Shroom Burger | W 9.4 | 몬스터 치즈와 체다 치즈로 속을 채운 베지테리안 버거
3. Cheeseburger  | W 6.9 | 포테이토 번과 비프패티, 치즈가 토핑된 치즈버거
4. Hamburger     | W 5.4 | 비프패티를 기반으로 야채가 들어간 기본버거
0. 뒤로가기      | 뒤로가기
0 <-

[ SHAKESHACK MENU ]
1. Burgers         | 앵거스 비프 통살을 다져만든 버거
2. Forzen Custard  | 매장에서 신선하게 만드는 아이스크림
3. Drinks          | 매장에서 직접 만드는 음료
4. Beer            | 뉴욕 브루클린 브루어리에서 양조한 맥주
0. 종료            | 프로그램 종료
0 <-

프로그램을 종료합니다.

위와 같은 프로그래밍을 구현하기 위해 처음 생각한 방법은 조건 문안에 조건문을 사용하여 구현하는 방식이었지만 아래와 같은 코드를 사용하니 코드가 간결해진 것을 확인할 수 있다.

 

/// main.swift
import Foundation
let app = App.shared
let io: IOInterface = TerminalInterface.shared

app.register(option: BurgerMenu())
app.register(option: FrozenCustardMenu())
app.register(option: DrinkMenu())
app.register(option: BeerMenu())

app.run(io: io)

 

/// app.swift

import Foundation
class App {
    static let shared = App()
    let io = TerminalInterface.shared

//    BurgerMenu(), FrozenCustardMenu(), DrinkMenu(), BeerMenu()
    var options: [Option] = []

    func register(option: Option) { options.append(option) }
    func run(
        io: IOInterface
    ) { while true {
        let option = chooseService(io: io)
        option.info()
    }}
    private func chooseService(io: IOInterface) -> Option {
        io.output("[ SHAKESHACK MENU ]")
        for i in options {
            io.output(i.info())
        }
        io.output("-------------------")
        let input = io.input("-> ", validate: {
            guard let value = Int($0) else { return false }
            return Int($0) != nil && (value > 0 && value <= self.options.count)
        })
        return options[Int(input)!]
    }

    private init() {}
}

 

/// menu.swift

import Foundation

protocol IOInterface {
    func output(_ message: String)
    func input(_ prompt: String, validate: ((String) -> Bool)?) -> String
    func wait()
}

extension IOInterface {
    func input(_ prompt: String) -> String {
        return input(prompt, validate: nil)
    }
}

class TerminalInterface: IOInterface {
    static let shared: TerminalInterface = .init()

    func output(_ message: String) {
        print(message)
    }

    func input(_ prompt: String, validate: ((String) -> Bool)? = nil) -> String {
        while true {
            print(prompt, terminator: "")
            guard let input = readLine() else { exit(1) }
            guard input.count > 0 else { continue }

            if validate?(input) != false {
                return input
            }
        }
    }

    func wait() {
        _ = readLine()
    }

    private init() {}
}

protocol Option {
    var id: Int { get }
    var name: String { get }
    var desc: String { get }
    func info() -> String
}

protocol Menu {
    var price: Int { get }
}

extension Option {
    func info() -> String {
        return "\(id). \(name)\t\t\t | \(desc)"
    }
}

protocol MenuOption: Option, Menu {}

extension MenuOption {
    func info() -> String {
        let price = String(format: "%.1f", self.price / 1000)
        return "\(id). \(name)\t\t\t | W \(price) | \(desc)"
    }
}

protocol Burger: MenuOption {}

protocol FrozenCustard: MenuOption {}

protocol Drink: MenuOption {}

protocol Beer: MenuOption {}
struct BurgerMenu: Option { var id: Int { 1 }
    var name: String { "Burger" }
    var desc: String { "맛있는 버거" }
}

struct ShackBurgerMenu: Burger {
    var id: Int { 1 }
    var name: String { "ShackBurger" }
    var desc: String { "토마토, 양상추, 쉑소스가 토핑된 치즈버거" }
    var price: Int { 6900 }
}

struct ShroomBurger: Burger {
    var id: Int { 2 }
    var name: String { "ShroomBurger" }
    var desc: String { "몬스터 치즈와 체다 치즈로 속을 채운 베지테리안 버거" }
    var price: Int { 9400 }
}

struct SmokeShack: Burger {
    var id: Int { 3 }
    var name: String { "SmokeShack" }
    var desc: String { "베이컨, 체리 페퍼에 쉑소스가 토핑된 치즈버거" }
    var price: Int { 8900 }
}

struct Cola: Drink {
    var id: Int { 1 }
    var name: String { "Cola" }
    var desc: String { "코카콜라" }
    var price: Int { 2500 }
}

struct Cider: Drink {
    var id: Int { 2 }
    var name: String { "Cider" }
    var desc: String { "사이다" }
    var price: Int { 2500 }
}

struct FrozenCustardMenu: Option { var id: Int { 2 }
    var name: String { "FrozenCustard" }
    var desc: String { "맛있는 아이스크림" }
}

struct DrinkMenu: Option { var id: Int { 3 }
    var name: String { "Drink" }
    var desc: String { "맛있는 음료" }
}

struct BeerMenu: Option { var id: Int { 4 }
    var name: String { "ShackBurger" }
    var desc: String { "맛있는 맥주" }
}

struct VanilaFrozenCustard: FrozenCustard {
    var id: Int { 1 }
    var name: String { "Vanila Frozen Custard" }
    var desc: String { "바닐라맛 Vanila Frozen Custard" }
    var price: Int { 5500 }
}

struct ChocolateFrozenCustard: FrozenCustard {
    var id: Int { 2 }
    var name: String { "Chocolate Frozen Custard" }
    var desc: String { "초콜릿맛 Chocolate Frozen Custard" }
    var price: Int { 5500 }
}

struct Lager: Beer {
    var id: Int { 1 }
    var name: String { "Lager" }
    var desc: String { "브루클린 라거" }
    var price: Int { 4500 }
}

struct BrownAle: Beer {
    var id: Int { 1 }
    var name: String { "BrownAle" }
    var desc: String { "상큼한 브라운 에일" }
    var price: Int { 4500 }
}

(위 코드들은 아직 첫 번째 메뉴와 입력만 받을 수 있는 코드이다.)