[Java] CoffeeMaker V1

주영·3일 전

요구사항

실제 모델 : 콤비 커피머신

  • 커피머신은 에스프레소를 추출하는 머신과 드립커피를 내리는 머신이 있다
  • 메뉴는 에스프레소, 아메리카노, 라떼가 있다
    → 각 음료는 각자의 준비 방법을 알고 제작된다
  • SOLID와 DI, IoC 개념을 적용하여 만들자!

객체지향설계

CoffeeMachine

커피를 어떻게 만드는지는 모르고 커피를 brew()하는 역할만 하는 것 → 인터페이스 사용

  • EspressoMachine은 brew()를 구현해 에스프레소를 추출함
  • DripCoffeeMachine은 brew()를 구현해 드립 커피를 내림

❋ MilkFrother 클래스도 CoffeeMachine을 구현하게 할까? → brew() 기능이 필요 없으니까 X... → 이 기능은 별도의 장치가 수행하는 역할이라고 생각해서 독립된 하나의 클래스로 생성하게 되었다 (CoffeeMachine은 커피를 brew()하는 역할만!)

Coffee

음료는 스스로 제조 방법을 알고 있고 prepare()을 각자 구현하도록 함 → 인터페이스 사용

❋ Coffee는 인터페이스가 좋을까? 추상클래스가 좋을까? Coffee를 행동 중심으로 볼 것이라면 인터페이스, 데이터를 중심으로 본다면 추상클래스가 적합하다
▷ 행동 중심 : 커피가 스스로 제조방법을 알고 있어서 커피를 스스로 준비한다 → Coffee를 준비하는 행동의 책임을 가진다고 판단, 각 음료가 준비라는 행동을 구현하면 되는 것
▷ 데이터 중심 : 커피 원두 및 재료를 준비하고 바리스타가 만든다 → Coffee는 커피 원두 및 재료 같은 필드를 통해 필요한 데이터를 가지고 있는 것이고, 제조하는 책임은 바리스타에게 넘기는 것

  • Espresso는 EspressoMachine을 이용하고, prepare()을 구현해 에스프레소를 준비함
  • Americano는 EspressoMachine을 이용하고, prepare()을 구현해 아메리카노를 준비함
  • Latte는 EspressoMachine과 MilkFrother을 함께 이용하고, prepare()을 구현해 라떼를 준비함

CoffeeMaker

음료를 주문하고 생성된 결과를 얻는 음료 주문 제작기 역할
특정 음료를 만드는 것이 아니라 Coffee 역할을 수행하는 모든 객체를 받아서 prepare()을 호출할 수 있음
Coffee 객체를 직접 생성하는 것 X → setter 방식으로 외부에서 전달 받는 것

적용된 원칙
① SRP : 커피 제조 과정에서 역할을 음료 준비, 커피 추출, 우유 거품 생성 등으로 분리하여 각 클래스가 하나의 책임을 담당하고 있다
② OCP : Coffee, CoffeeMachine 인터페이스를 이용하여 새로운 음료나 장비가 추가되더라도 기존 코드를 변경할 필요가 없다
③ LSP : Coffee와 CoffeeMachine의 각 구현체를 해당 인터페이스 타입으로 대체하여도 정상적으로 동작한다
④ ISP : 음료 준비와 커피 추출 역할을 Coffee, CoffeeMachine으로 분리하여 객체가 필요하지 않은 기능에 의존하지 않도록 하였다
⑤ DIP : CoffeeMaker와 각 음료들이 구체 클래스가 아니라 Coffee, CoffeeMachine인터페이스에 의존하며, 필요한 구현 객체는 외부에서 주입받도록 하였다

✴︎ IoC / DI : CoffeeMaker와 각 음료들이 필요한 객체를 직접 생성하지 않고 객체의 생성이나 선택에 대한 제어권을 외부(Main)에 넘기고 있다 / 필요한 객체를 외부에서 setter 방식 또는 생성자 방식을 통해 주입하고 있다

⇒ 결국에는 전부 연결되는 개념임을 코드를 통해 인식할 수 있다!

구현

❋ 왜 여기서 CoffeeMachine 필드 앞에 private, final을 붙인 건가? → 알다시피 private은 CoffeeMachine을 외부에서 접근하지 못하도록 캡슐화하는 거고 final은 생성자를 통해 주입받은 CoffeeMachine이 이후에 다른 객체로 변경되지 못하게 만드는 거다 → 그냥 결론적으로는 둘다 코드가 이상하게 작동하지 않도록 하고 설계 의도를 명확하게 하려고 쓰는 것

다시 정리하자면
→ CoffeeMaker 클래스는 Coffee 인터페이스 타입의 필드를 가지고 있고 setter를 통해서 Coffee를 구현한 객체를 받을 수 있다
→ makeCoffee 메소드를 통해서 prepare()을 호출함으로써 Coffee 타입의 객체들을 전부 다 만들 수가 있다
⇒ 이러면 구체적인 객체에 대한 의존성을 낮추어 지금 의존하고 있는 객체를 바꿀 때 편리하다

→ EspressoMachine, DripCoffeeMachine, milkFrother 객체 생성한다
→ Espresso, Americano, Latte 객체 생성, 이때 생성자 주입을 통해 외부에서 생성된 객체를 넣어주고 있다
→ 생성된 객체에 직접 prepare()을 호출하면 각 음료가 생성된다
→ CoffeeMaker을 사용하면 setter 주입을 통해 외부에서 생성된 객체를 넣을 수 있고 makeCoffee()를 통해 해당 객체의 prepare()을 하여 음료를 생성한다

실행시켜보면 ? 에스프레소 아메리카노 라떼 전부 다 잘 나오는 걸 볼 수가 있다^..^

간략한 소감

이번 실습을 통해서 SOLID를 적용하려고 하면서 인터페이스나 클래스를 어떻게 만들면 좋을지에 대해서도 배울 수 있었다
인터페이스와 추상클래스를 사용하는 상황에 대한 판단이나, 인터페이스를 어떤 기준으로 분리하면 좋을지에 대한 생각을 해보기도 했다
특히 IoC, DI는 어떻게 코드에서 사용되는지를 보면서 setter주입이나 생성자 주입을 왜 사용하는지에 대해서도 알게 되었다
전체적으로 인터페이스나 클래스들의 관계에 대해서 많이 생각해보게 되었던 것 같다
어떤 식으로 연결이 되어있어야 하는지 상속, 구현, 의존 이런 개념들을 다시 한 번 정리해보는 시간을 가질 수 있었다
역시 백문이 불여일습 ! ! !

0개의 댓글