My Melody Is Cute BurgerQueen 주문 프로그램 만들기 5
본문 바로가기

실습👁️‍🗨️/연습

BurgerQueen 주문 프로그램 만들기 5

 

🌼객체지향 원리적용

 

🌼문제 원인 분석

문제점 : 필연적인 변화가 생기면 부수적으로 변경해야할 코드가 많다

원인 : 특정 객체가 어떤 객체를 직접 생성해서 직접 사용할지 결정한다 = 의존성이 높다

 

🌼역할과 구현의 분리

역할 : 구현 = 배역 : 배우

연극연습을 할때 상대 배우에 맞춰서 연습하는게 아니라 대본속의 상대배역에 집중해서 연습야한다

배우에 맞춰서 했다가 배우가 바뀌면 다시 새로 연습해야한다 (배우에 의존)

지금까지 작성한 코드도 모두 구현에 의존하고 있다

특정역할이 아니라 그 역할을 수행하는 구체적인 클래스에 의존

  • CozDiscountCondition은 할인 정책이라는 역할이 아니라, FixedRateDiscountPolicy라는 구체적인 구현 클래스에 의존하고 있습니다.
  • KidDiscountCondition은 할인 정책이라는 역할이 아니라, FixedAmountDiscountPolicy라는 구체적인 구현 클래스에 의존하고 있습니다.
  • Order는 할인 조건이라는 역할이 아니라, CozDiscountCondition과 KidDiscountCondition이라는 구체적인 구현 클래스에 의존하고 있습니다.

원인 : 특정 객체가 어떤 객체를 사용할지 직접 결정한다

-> 역할이 아니라 구현에 의존하고 있다

역할에 의존하게 만들기

할인정책,할인조건에 해당하는 역할을 만들고 해당 역할에 의존하도록 하면 된다

인터페이스를 사용한다

인터페이스를 어떻게 활용하고 장점이 무엇인지 알아보기

 

🌼리팩토링

 

특정 역할을 인터페이스로 정의해두고

인터페이스를 implements 하는 객체라면 어떤 객체든 화살표 우항의 역할을 수행할 수 있게 하는 것

역할에 의존하도록

CozDiscountCondition → DiscountPolicy 인터페이스

KidDiscountCondition → DiscountPolicy 인터페이스

Order → DiscountCondition 인터페이스

 

🍀수행 해야하는 작업

 

1. DiscountPolicy 인터페이스 정의

DiscountPolicy 인터페이스를 만들고 calculateDiscountPrice()를  추상 메서드로 등록

DiscountPolicy를 구현하도록 해줌

인터페이스는 역할

FixedRateDiscountPolicy , FixedAmountDiscountPolicy 는 구현

 

2. CozDiscountCondition → DiscountPolicy 인터페이스

인스턴스를 저장하는 필드을 삭제하고 인터페이스타입의 필드를 정의후 생성자를 만들어준다

applyDiscount()에서 fixedRateDiscountPolicy가 아니라 방금 정의한 discountPolicy에서 메서드를 참조하게 수정

DiscountPolicy가 FixedRateDiscountPolicy이던, FixedAmountDiscountPolicy이던,

인터페이스에 정의된 메서드 calculateDiscountedPrice()를 사용할 수 있게 되었음을 의미합니다.

??

 

의존성 주입 : 객체가 자신이 의존할 객체를

스스로 만들도록 하는것이 아니라 외부에서 주입해주는 것

방금 코드를 수정함으로써 CozDiscountCondition이 스스로 DiscountPolicy 객체를 생성해서 사용하지 않게 되었으며,

CozDiscountCondition 객체가 생성되는 시점에 DiscountPolicy역할을 수행할 객체를 외부로부터 생성자를 통해 주입받게 되었다.

의존성 주입의 기반 원리는 추상화와 다형성

  1. CozDiscountCondition은 DiscountPolicy의 구현 클래스가 무엇이더라도, 인터페이스에 정의된 메서드를 통해 calculateDiscountedPrice()를 사용할 수 있게 됨
  2. 또한, 할인 정책을 바꿀 때도 CozDiscountCondition의 코드를 전혀 수정하지 않아도 된다.
  3. 코드스테이츠 수강생에게 고정 금액을 할인하도록 이벤트가 변경된다면 makeOrder()에서 그저 CozDiscountCondition을 인스턴스화할 때 생성자의 인자만 바꿔주면 된다.
CozDiscountCondition cozDiscountCondition = new CozDiscountCondition(new FixedAmountDiscountPolicy(500));

 

3. KidDiscountCondition → DiscountPolicy 인터페이스

위와 같은 맥락으로 수정

 

4. DiscountCondition 인터페이스 정의

인터페이스를 생성하고 외부에서 사용하는 메서드들을 추상메서드로 정의

인터페이스를 구현하도록 해줌

 

5. Order → DiscountCondition 인터페이스 

Order에 필드 discountCondition 정의 할인조건은 여러개니까 배열로 정의

생성자 수정 (생성자를 통해 객체 배열을 주입받을것)

주입된 객체 배열인 discountCondition을 사용하도록 하기

checkDiscountCondition()

기존에는 CozDiscountCondition과 KidDiscountCondition 인스턴스를 직접 생성해서 따로 따로 checkDiscountCondition()을 호출함

객체 배열인 discountCondition을 순회하면서 요소를 통해 checkDiscountCondition()을 호출하도록 해줄 것

applyDiscount()

기존에는 CozDiscountCondition 과 KidDiscountCondition 인스턴스를 통해 직접 applyDiscount()를 호출했음

반복문을 순회하면서 각 요소를 통해 applyDiscount()를 호출하도록 코드 수정

 

이제 할인 조건이 추가되어도 Order클래스의 코드는 바꿀 필요가 없다

새로운 할인 조건을 적용 한다면 새클래스를 정의하고 Order 생성자의 인자로 인스턴스를 전달 하면 된다

 

객체의 자율성 높이기

자율적인 객체가 되어야 한다

자신이 보유한 데이터와 관련된 기능을 자율적으로 처리할 수 있어야하며

다른 객체와 유기적인 협력관계에 참여할 수 있어야 함

= 객체의 역할이 뚜렷해야하며 자신의 역할에만 충실하면서 다른 객체와 적절하게 협력을 해야한다

Order가 makeOrder()에서 Order의 역할이 아닌 기능을 수행한다

주문 관련된 코드만 가지고 있어야 하는데, 할인과 관련된 로직을 수행하는 코드도 가지고 있다

할인과 관련된 기능을 할인과 관련된 클래스로 옮겨야 한다 - 새로운 Discount클래스를 만들고 내부로 옮기기

옮겨야하는 할인로직은 Order에 정의해둔 필드인 discountCondition을 필요로 한다

동일하게 필드를 정의해주고 생성자를 주입받게 해줘야한다.

OrderApp 에서 Order를 인스턴스화 할때 생성자의 인자도 수정한다

Order는 할인과 관련한 어떤일이 일어나는지 전혀 알지 못한다

그저 discount에 discount() 메서드가 정의되어있고 사용하기만 할뿐이다

객체의 세부적인 동작을 객체 내부로 감추로 외부로는 객체의 메서드를 사용할 수 있는 최소한의 통로만 열어두는 것  = 캡슐화

객체의 자율성과 외부로부터 객체 내부로의 접근을 적절히 제한시켜 객체간의 결합도를 낮춘다

 

단일 책임 원칙 (Single Responsibility Principle , SRP)

객체는 오직 하나의 책임만 맡아야 한다

 

OrderApp은 두가지 책임을 맡고 있다

1. 프로그램의 주요 흐름 담당 2. 프로그램 동작에 필요한 모든 객체 생성

다른 객체로 분리를 시켜주는게 좋다

프로그램 동작에 필요한 모든 객체를 생성하고,

의존 관계를 맺어주는 역할을 하는 클래스를 정의해 보자

OrderApp에서 의존성 주입을 통해 필요한 객체를 주입받도록 하기

- OrderApp에 각 객체를 주입받아 저장할 필드 정의

- OrderApp에 필요한 객체를 주입받을 생성자 정의

- main메서드에서 Appconfigurer를 인스턴스화

- OrderApp의 생성자에 AppConfigurer에 정의한 메서드들을 사용하여 필요한객체들을 인자로 전달하면서 OrderApp을 인스턴스화


어떻게 아래 사항이 가능한 것인지 생각해보기

  • 새로운 할인 이벤트를 진행하는 경우
    • XXXXDiscountCondition 클래스만 새롭게 정의한 다음, AppConfigurer의 discount() 메서드의 return문 내 DiscountCondition 배열에 새로운 클래스의 인스턴스를 추가하기만 해 주면 됩니다.
    • 새로운 클래스를 필요에 따라 새롭게 만들었을지언정, 기존에 작성한 코드들을 수정하지 않아도 됩니다. 
  • 할인 조건에 따른 할인 정책을 바꾸는 경우
    • 즉 코드스테이츠 수강생에게 고정 금액 할인을, 청소년에게 고정 비율 할인을 적용하는 경우, AppConfigurer의 discount() 메서드 내에서 new FixedRateDiscountPolicy(10)와 new FixedAmountDiscountPolicy(500)의 위치만 서로 바꿔주면 됩니다.
    • 할인 정책과 관련된 클래스를 사용하는 CozDiscountCondition과 KidDiscountCondition의 코드를 변경하지 않아도 됩니다.

- 인터페이스를 생성해 각각의 기능역할을 하는 서로 다른 객체를 사용할수 있어서 ?

 

❗아직 남은 문제점 : 장바구니에 상품을 담고 주문하면 장바구니에 담은 상품이 보이지 않음

Main에서 cart()를 통해 만든 Cart인스턴스와

AppConfigurer의 order() 내 cart() 를 통해 만들어진 Cart 인스턴스는

주소값이 다른 별개의 인스턴스가 된다는 것이 문제

OrderApp의 Cart 인스턴스 → Main에서 OrderApp을 인스턴스화할 때, appConfigurer.cart()를 호출함으로써 만들어짐

Order의 Cart 인스턴스 → Main에서 OrderApp을 인스턴스화할 때, appConfigurer.order()를 호출하고 있고, AppConfigurer의 order()의 return문 내에서 cart()가 한 번 더 호출됨으로써 만들어짐

서로 주소값이 다른 Cart 인스턴스가 두개 생성됨

변수인 items값이 달랐을 것

  • 문제 원인 : OrderApp의 cart에 할당된 인스턴스(1)와 Order의 cart에 할당된 인스턴스(2)가 다르다.
    • 원인의 원인 : Main에서 appConfigurer.cart()를 통해 (1)을 생성하고 있고, Main에서 appConfigurer.order()가 실행되는 과정에서 (2)가 만들어진다.

해결법 : Cart의 인스턴스가 단 한 번만 만들어지도록 해야 하며, 이 하나의 인스턴스를 OrderApp과 Order가 공유하게 해준다

 

AppConfigurer에 cart 필드를 정의한 다음, 바로 초기화

단하나의 객체만 생성되도록 코드를 작성하는 패턴 = 싱글톤 패턴(Singleton pattern)

 

 

🍨느낀점

프로그램이 어떻게 흘러가는지 흐름과 전체적인 구성을 보는 법을 배운것같다

중간에 기능구현 부분이 어려워서 시간을 엄청 소비했다,, 특히 장바구니ㅠ 처음 접해봐서 그런거라고 생각한다ㅠㅠ

배웠던 내용을 활용해서 사용되어지는 부분들이 많은데 이해가 가는곳도 있고 아직 잘 모르겠는 부분들도 있다...

역시나 클래스들이 많고 거기서 만든 객체를 또 캡슐화 하다보니까 더 헷갈리게 느껴진다ㅎㅎ후

뒷부분은 진짜 모르겠따 의존성주입...??? 스프링에 나오는 내용이라는데,,,스프링도 미리 봐야겠다

다시 처음부터 보면서 이해 해봐야할것같다.. 여러번 하다보면 익혀질거라고 믿는다!