클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙 정리
정리
- 객체 지향의 핵심은 다형성
- 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
- 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.(OCP 위반)
- 다형성 만으로는 OCP, DIP를 지킬 수 없다.
※ ODC, DIP를 코드를 통해 이해하기
아래와 같은 코드들이 있을때,
OrderService : 주문 인터페이스
OrderServiceImpl : OrderService의 구현체
DiscountPolicy : 할인관련 인터페이스
FixDiscountPolicy : DiscountPolicy 구현체(정액 할인 정책)
RateDiscountPolicy : DiscountPolicy 구현체(정률 할인 정책)
//OrderServiceImpl.java
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
//문제점
OCP 위반 : 할인 정책을 정액 -> 정률로 변경하려면
new FixDiscountPolicy()를 new RateDiscountPolicy()로 변경해야한다.
소스 코드 수정 필요.
DIP 위반 : OrderServiceImpl.java 소스코드에 인터페스만 의존해야하는데 구현클래스도 의존하고있다.
private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); 이부분을 보면
인터페이스인 DiscountPolicy와 구현체인 FixDiscountPolicy를 의존하고 있음.
//AppConfig.java (구현 객체를 생성하고 연결 책임 클래스)
public class AppConfig {
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
//OrderServiceImpl.java
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
//생성자 주입
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
//OrderApp.java
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 20000);
}
}
//설명
구현 객체를 생성하고 연결하는 책임을 가지는 AppConfig.java 클래스 별도 생성.
결국 AppConfig로 인해 서비스에서는 인터페이스만 의존하고 구현체의 변경에 대해서는
신경을 쓸 필요가 없다.
OCP 보완 : 할인 정책 변경시 서비스 코드를 건들필요 없이 AppConfig.java 소스 수정만으로 해결
DIP 보완 : OrderServiceImpl는 DiscountPolicy라는 인터페이스만 의존하고,
해당 인터페이스의 구현클래스가 무엇인지 알지못한다.
//AppConfig.java (구현 객체를 생성하고 연결 책임 클래스)
@Configuration
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
//OrderApp.java
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 20000);
System.out.println("order = " + order.toString());
System.out.println("order.calculatePrice() = " + order.calculatePrice());
}
}
// 설명
- ApplicationContext를 스프링 컨테이너라 한다.
- ApplicationContext는 인터페이스이다.
- 구현체는 new AnnotationConfigApplicationContext(AppConfig.class);
- 기존에는 개발자가 AppConfig를 사용해 직접 객체를 생성하고 DI를 했지만,
이제부터는 스프링 컨테이너를 통해서 사용한다.
위와 같이 @Bean을 통해 스프링 컨테이너에 객체를 등록하고 애플리케이션 구동하면 아래와 같은 로그가 뜬다.
※ 참 고
이전에는 bean설정을 xml에서 하였지만 요즘엔 위와 같이 자바 @Configuration 코드로 설정한다.
이렇게 @Configuration코드로 하면 factoryBean을 통해 bean을 등록하는 방식이다.
또한, 스프링 컨테이너를 사용하면 기본적으로 객체를 모두 싱글톤으로 만들어서 관리해준다.
제어의 역전 IoC(Inversion of Control)
위의 OCP, DIP 보완 코드를 보면 OrderServiceImpl은 필요한 인터페이스만 호출하고 AppConfig가 프로그램의 제어 흐름을 가져간다.
즉 OrderServiceImpl는 묵묵히 자신의 로직에만 신경쓰고 실행한다.
프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것이 제어의 역전(Ioc)이다.
※ 참 고(프레임워크 vs 라이브러리)
프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 프레임워크다.(JUnit)
내가 작성한 코드가 직접 제어의 흐름을 담당한다면 라이브러리다.
의존관계 주입 DI(Dependency Injection)
애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다. (OCP, DIP 보완 코드의 AppConfig를 통해 주입)
URL과 URI의 차이 (0) | 2021.08.12 |
---|---|
애자일 소프트웨어 개발 선언 (0) | 2021.07.29 |
OAuth/JWT (0) | 2021.07.24 |
기술면접 (0) | 2021.05.04 |
clean code 3가지 사항 (0) | 2021.04.29 |