쥬스메이커 프로젝트 저장소입니다.
Tommy | Jin |
---|---|
![]() |
|
@angryeon7 | @wlsdud-dev |
과일저장소 객체의 과일 재고를 관리하여 생과일 쥬스를 만드는 프로그램
Type | 기능 |
---|---|
Fruits | 쥬스를 만드는데 사용되는 과일들을 담은 Enum |
Juice | - 만들어지는 쥬스들을 관리하는 Enum - Recipe 라는 computed property 를 사용하여, 쥬스를 선택하면, 레시피 별로 필요한 과일의 재고를 return 해주는 Dictionary 형태로 구현 |
FruitStore | - 현재 프로젝트에서는 과일재고 객체가 하나만 존재한다는 것을 보장하기 위해 싱글톤 적용 |
JuiceMaker | - FruitStore을 가져와서 쥬스를 만드는 기능만 담은 구조체 |
JuiceMakerError | - 재고 부족가 부족할때, 에러문구를 띄어주는 Error 타입 |
Type | 기능 |
---|---|
JuiceMakerViewController | - 화면간 데이터 전달을 위한 NotificationCenter 를 화면 초기 실행시 호출되는 함수 viewDidLoad에 addObserver - 각 쥬스 버튼을 클릭할 때 재고가 충분하다면, 재고가 실시간으로 수정되고 성공 Alert 실행 - 재고가 충분하지 않다면, 재고를 수정하는 Modal 이동 |
FruitStockViewController | - 각 Stepper에 값이 변경될때마다 Label이 변경되게 하는 함수 구현 - 닫기 버튼을 클릭하였을때, Model의 FruitStore()로 NotificationCenter post 를 보내게 하여 JuiceOrderViewController()의 값이 변하게 하는 함수 구현 |
PR #77 Step1 쥬스 메이커 타입 정의
- Fruits - 과일의 종류에 대한 열거형 생성
- Juice - 쥬스의 종류에 대한 열거형 생성
- 각 쥬스 제조에 필요한 정해진 개수의 과일 목록을 제시(recipe)
- FruitStore 과일별 재고를 저장, 확인 , 변경
- JuiceMaker - 현재 주문한 쥬스의 제조가 가능한지 판별
- 레시피에 따라 재고 감소
- 쥬스 자판기에서 발생하는 에러 처리
- 에러메세지의 가독성 향상
- 연산 프로퍼티를 사용
- 접근제어자 추가
- private(set)을 통해 수정이 불가능하게 막아 외부에서의 재고 수정을 방지
- final을 사용하여 static dispatch가 가능해 메모리 효율이 좋아짐
- 클래스 당 하나의 파일에 작성되도록 수정
실행 결과
PR #97 Step2 초기화면 기능구현
- '재고 수정' 버튼을 터치하면 '재고 추가' 화면으로 이동
- 각 주문 버튼 터치 시
- 쥬스 재료의 재고가 있는 경우 : 쥬스 제조 후
“*** 쥬스 나왔습니다! 맛있게 드세요!”
얼럿 표시 - 쥬스 재료의 재고가 없는 경우 :
“재료가 모자라요. 재고를 수정할까요?”
얼럿 표시- ‘예' 선택시 재고수정 화면으로 이동
- ‘아니오' 선택시 얼럿 닫기
- 쥬스 재료의 재고가 있는 경우 : 쥬스 제조 후
개선한 내용
-
버튼 식별
여러개의 버튼을 어떻게 식별하여 명세서 대로의 기능으로 구현할 수 있을지 고민하였다.
- 버튼의 tag를 부여해 구별을 하였지만 가독성이 떨어지는것 같아, 버튼의 TitleText로 식별하여 버튼의 처리를 수행했다.
하나의 메서드가 버튼을 식별하고 식별한 버튼에 따라 기능을 처리 한다면, SRP를 위배하고 추후 특정 버튼에서만 로직이 추가되어야 하는 경우 문제가 발생 할 수 있다.
@IBAction private func touchUpStrawberryBananaJuiceOrderButton(_ sender: Any) { orderJuice(juice: .strawberryBanana) }
-
화면 전환
쥬스메이커 뷰에서 재고수정 뷰로 화면전환을 어떤 방식으로 할지 고민하였다.
- 네비게이션 뷰, 모달 뷰 어떤 것으로 만들지 고민 하였고 논의 끝에 재고수정은 깊이와 흐름을 가지기 보다는 쥬스메이커와는 다른 흐름이라는 결론으로 모달뷰로 만들었다.
- 화면전환 구현에 세그, 프레젠트 호출 중 고민하였다. 스토리보드를 최대한 활용하고 코드의 깔끔함을 위해 세그를 사용해 구현하였다. (이름 짓기가 어려웠다.)
@IBAction private func addButton(_ sender: UIButton) { guard let fruitStockViewController = storyboard?.instantiateViewController(identifier: "fruitStockViewController") as? FruitStockViewController else { return } self.present(fruitStockViewController, animated: true, completion: nil) }
PR #110 Step3 재고 수정 기능구현
- 스토리보드 AuotoLayout 적용
- IBOutlet, IBAction등을 이용하여 스토리보드와 코드 연결
- 화면 제목 '재고 추가' 및 '닫기' 버튼 구현
- 닫기를 터치하면 이전화면으로 복귀
- 화면 진입시 과일의 현재 재고 수량 표시
- stepper를 통한 재고 수정
화면간의 데이터 이동
final class JuiceMakerViewController: UIViewController, FruitStockDelegate {
func didUpdateFruitStock(fruitStock: [Fruits : Int]) {
fruitStock.forEach { (fruit,value) in
let currentFruit = juiceMaker.fruitStore.quantity(of: fruit)
let difference = value - currentFruit
juiceMaker.fruitStore.changeStock(fruitName: fruit, amount: difference)
}
configureView()
}
//...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let navigationViewController = segue.destination as? UINavigationController else { return }
if let fruitStockViewController = navigationViewController.visibleViewController as? FruitStockViewController {
fruitStockViewController.fruitStock = juiceMaker.fruitStore.fruitStock
fruitStockViewController.delegate = self
}
}
}
protocol FruitStockDelegate: AnyObject {
func didUpdateFruitStock(fruitStock: [Fruits: Int])
}
//...
@IBAction func closedStockViewButton(_ sender: UIBarButtonItem) {
delegate?.didUpdateFruitStock(fruitStock: fruitStock)
self.dismiss(animated: true, completion: nil)
}
현재 프로젝트에서는 과일재고객체가 하나만 존재한다는 것을 보장하기 위해 싱글톤이 적절한 패턴이라고 생각함 (서버와 클라이언트의 관계)
과일의 재고를 소모하고 충전하므로 과일 창고의 또 다른 객체가 생성될 필요가 없다고 판단.
- 싱글톤 패턴 적용
- Notification을 이용하여 뷰 업데이트 구현
화면간의 데이터 이동
final class JuiceMakerViewController: UIViewController {
private let juiceMaker = JuiceMaker()
@IBOutlet private weak var strawberryLabel: UILabel!
@IBOutlet private weak var bananaLabel: UILabel!
@IBOutlet private weak var kiwiLabel: UILabel!
@IBOutlet private weak var pineappleLabel: UILabel!
@IBOutlet private weak var mangoLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
configureView()
NotificationCenter.default.addObserver(self,
selector: #selector(configureView),
name: .update,
object: nil)
}
//...
}
extension Notification.Name {
static let update = Notification.Name("update")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.post(name: .update, object: nil)
}
- 약한 참조
- 클로저는 외부에 프로퍼티, 메서드의 값을 캡처 하기 때문에
self
에 강한 참조가 일어남. 때문에 강한 순환참조가 일어나[weak self]
로 강한 순환참조를 끊어줌.
- 클로저는 외부에 프로퍼티, 메서드의 값을 캡처 하기 때문에
- 기능분리을 통해 객체지향적 코드 작성
- 메소드가 한가지 일만 할 수 있도록 수정.