객체지향 프로그래밍에 있어 객체란 그냥 객체가 아니라, 자율적인 객체가 되어야 한다. 즉, 객체지향 프로그래밍에서의 객체는 자신이 보유한 데이터와 관련된 기능을 자율적으로 처리할 수 있어야 하며, 그러면서도 다른 객체와 유기적인 협력 관계에 참여할 수 있어야 한다.
다시 말해, 객체들이 맡아야 할 역할이 뚜렷하게 정해져야 하며, 각 객체는 오로지 자신의 역할에만 충실하면서도, 다른 객체와 적절하게 협력해야 한다는 의미이다.
Order는 주문과 관련된 코드만 가지고 있어야 하는데, 할인과 관련된 로직을 수행하는 코드도 가지고 있다는 점이 문제이다.
따라서 Order에서 수행하고 있는 할인과 관련된 기능을 할인과 관련된 어떤 클래스로 옮겨야 한다. 이를 위해 discount 패키지에 Discount 클래스를 하나 새로 만들고, 문제가 되던 기능을 Discount 클래스 내부로 옮긴다.
Discount로 옮겨야 하는 할인 로직은 Order에 정의해 둔 필드인 discountConditions를 필요로 한다. 따라서, Discount 클래스에 동일하게 discountConditions 필드를 정의해 주고, 생성자를 통해 주입받을 수 있도록 해주어야 한다. 아래와 같이 정의한다.
그다음, Order에 있던 메서드들을 참고하여 Discount 클래스의 메서드로 정의한다.
이제 할인과 관련된 기능은 Discount로 모두 모여졌다. 변경된 사항을 Order에도 적용해 보자.
로직이 makeOrder()에서 Discount로 이동했으므로, makeOrder()도 수정해주어야 한다. discount를 통해서 할인 조건을 검사하고, 할인을 적용해 줄 수 있도록 하자.
마지막으로, OrderApp에서 Order를 인스턴스화할 때, 생성자의 인자를 수정
이처럼 객체의 세부적인 동작을 객체 내부로 감추고, 외부로는 객체의 메서드를 사용할 수 있는 최소한의 통로만 열어두는 기법이 바로 캡슐화이다. 캡슐화를 활용하면 객체의 자율성을 높일 수 있으며, 외부로부터 객체 내부로의 접근을 적절히 제한시켜 객체 간의 결합도를 낮출 수 있다.
단일 책임 원칙이라 함은 객체는 오직 하나의 책임만 맡아야 한다는 것이다.
지금부터는 아래와 같은 작업을 할것이다.
지금부터는 OrderApp에서 외부로부터 의존성 주입을 통해 필요한 객체를 모두 주입받도록 해줄 것이다.
1. OrderApp에 각 객체를 주입받아 저장할 필드 정의
OrderApp에 필요한 객체를 주입받을 생성자 정의
main메서드에서 AppConfigurer를 인스턴스화
OrderApp의 생성자에 AppConfigurer에 정의한 메서드들을 사용하여 필요한 객체들을 인자로 전달하면서 OrderApp을 인스턴스화
이제, main() 메서드에서 AppConfigurer를 인스턴스화한 후, 아래와 같이 OrderApp의 인자를 채워준다.
새로운 클래스를 필요에 따라 새롭게 만들었을지언정, 기존에 작성한 코드들을 수정하지 않아도 된다.
할인 정책과 관련된 클래스를 사용하는 CozDiscountCondition과 KidDiscountCondition의 코드를 변경하지 않아도 된다.
원인은 지금까지 작성한 AppConfigurer에 있습니다. 문제가 되는 것은 주문하기를 눌렀을 때 장바구니가 비어있다는 것이니, 주문과 관련된 Order 인스턴스를 만드는 order()를 살펴보자.
order()를 보면, Order 인스턴스를 생성하면서 cart()를 호출하고 있다. 즉, Main에서 cart()를 통해 만든 Cart 인스턴스와 AppConfigurer의 order() 내 cart()를 통해 만들어진 Cart 인스턴스는 주소값이 다른 별개의 인스턴스가 된다는 것이 핵심 문제 원인이다.
1. OrderApp의 Cart 인스턴스 → Main에서 OrderApp을 인스턴스화할 때, appConfigurer.cart()를 호출함으로써 만들어진다.
이러한 이유로 장바구니에 넣었던 상품이 주문하기를 눌렀을 때 출력되지 않았던 것이다. 서로 주소값이 다른 Cart 인스턴스가 두 개가 생성되었으니, 그 안의 인스턴스 변수인 items의 값이 달랐을 것이기 때문이다.
문제 상황 : 주문하기를 실행했을 때, 장바구니에 담은 내역이 보이지 않는다.
문제 원인 : OrderApp의 cart에 할당된 인스턴스(1)와 Order의 cart에 할당된 인스턴스(2)가 다르다.
원인의 원인 : Main에서 appConfigurer.cart()를 통해 (1)을 생성하고 있고, Main에서 appConfigurer.order()가 실행되는 과정에서 (2)가 만들어진다.
해결 방법 : Cart의 인스턴스가 단 한 번만 만들어지도록 해야 하며, 이 하나의 인스턴스를 OrderApp과 Order가 공유하도록 하면 된다.
Cart 인스턴스가 단 한 번만 생성될 수 있도록 하기 위해, AppConfigurer에 cart 필드를 정의한 다음, 바로 초기화를 진행했습니다. cart() 메서드는 단순히 cart의 값만 읽어서 리턴해주므로, 이제 몇 번이고 cart가 호출되어도 주소값이 동일한 Cart 인스턴스가 리턴되는 것을 보장할 수 있다.
이처럼 단 하나의 객체만 생성되도록 코드를 작성하는 패턴을 싱글톤 패턴(Singleton pattern)이라고 한다.