스프링은 제어의 역전, 의존관계 주입을 통해 다형성을 극대화해서 이용할 수 있게 도와준다. 스프링을 사용하면 역할과 구현을 편리하게 다룰 수 있는 것이다. 이처럼 스프링은 객체 지향 프로그래밍과 밀접한 관련이 있다.
객체 지향 설계의 5원칙 (SOLID 원칙)
1. SRP (Single Responsibility Principle: 단일 책임 원칙)
한 클래스는 하나의 책임만 가져야 한다.
여기서 말하는 하나의 책임이란 매우 모호하다. 클 수도 있고, 작을 수도 있고 문맥과 상황에 따라 달라진다. 따라서 이를 적절히 잘 조정하는 것이 기술이다.
이 책임이란 것에 대한 중요한 기준은 바로 "변경"이다. 변경이 생겼을 때 그에 따른 파급 효과가 적으면 SRP를 잘 따른 것이라고 볼 수 있다.
2. OCP (Open-Closed Principle: 개방 폐쇄 원칙)
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
확장을 하는데 기존 코드를 변경하지 않을 수 있을까?
이를 위해서 다형성을 활용해야 한다. 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현한다면, 확장은 했지만 변경은 생기지 않는다.
"역할"과 "구현"을 분리해서 생각해야 한다.
3. LSP (Liskov Subsititution Principle: 리스코프 치환 원칙)
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
다형성을 지원하기 위한 원칙이다. 하위 클래스는 인터페이스의 규약을 모두 지켜야 한다.
단순히 컴파일을 성공한다고 LSP를 지켰다고 볼 수 없다. 가격 책정 인터페이스의 '할인'이라는 기능을 가격이 상승하도록 구현하면 이 원칙을 위반하는 것이다. 아주 조금이라도 가격이 낮춰져야 한다.
4. ISP (Interface Segregation Principle: 인터페이스 분리 원칙)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
추상화된 인터페이스라고 해서 너무 큰 단위로 만드는 것도 좋지 않다. 인터페이스도 기능에 맞게 적당한 크기로 쪼개는 것이 중요하다.
예를 들어 자동차 인터페이스는 운전, 정비 인터페이스로 분리하고 사용자 클라이언트는 운전자, 정비사 클라이언트로 분리하게 되면, 정비 인터페이스가 변경되어도 운전자 클라이언트에 영향을 주지 않게 된다.
이렇게 되면 인터페이스가 명확해지고, 대체 가능성이 높아진다(확장성이 좋아진다).
5. DIP (Dependency Inversion Principle: 의존관계 역전 원칙)
프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
클라이언트는 구현 클래스에 의존하지 말고, 인터페이스에만 의존해야 한다. 그래서 유연하게 구현체를 변경할 수 있다. 구현제에 의존하게 되면 변경이 어려워진다.
다형성과 SOLID 원칙
객체 지향의 핵심은 다형성이다. 그러나 다형성만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다. 구현 객체를 변경할 때 어쩔 수 없이 클라이언트의 코드도 함께 변경되기 때문이다.
그말은 곧 다형성 만으로는 OCP, DIP를 만족할 수 없다는 것이다.
아래의 예시를 보자. 로미오와 줄리엣 연극에는 로미오 '역할'과 줄리엣 '역할'이 있다.
public class RomeoAndJuliet {
private RomeoRole romeoRole = new Minsoo();
private JulietRole julietRole = new Sujin();
}
이 코드에서는 로미오 역할에 민수라는 '배우(구현체)'가, 줄리엣 역할에는 수진이라는 '배우(구현체)'가 설정되어있다. 그런데 여기서 줄리엣 역할을 맡은 배우가 바뀐다면?
public class RomeoAndJuliet {
private RomeoRole romeoRole = new Minsoo();
// private JulietRole julietRole = new Sujin();
private JulietRole julietRole = new Jina();
}
위처럼 new Sujin() 대신 new Jina()로 코드를 변경해야한다. 즉 배우(구현 객체)를 변경하기 위해서 RomeoAndJuliet이라는 클라이언트의 코드를 변경해야하는 것이다. 분명히 다형성을 이용했지만 OCP 원칙을 지킬 수 없었다.
이를 해결하기 위해서는 객체를 생성하고 연관관계를 맺어주는 별도의 조립/설정자가 필요하다.
또한 로미오와 줄리엣이라는 연극이 로미오 역할 및 줄리엣 역할에 의존하면서, 이 역할을 맡는 배우에게도 의존하고 있다. 이는 클라이언트가 역할 뿐만 아니라 구현에도 의존하고 있는 것이기 때문에 DIP 원칙도 위반하고 있다.
캐스팅 디렉터가 별도로 존재한다면, 로미오와 줄리엣이라는 연극 자체(클라이언트)가 '민수', '수진', '진아' 등의 배우(구현)에 의존하지 않아도 될 것이다. 단지 이 연극에는 로미오/줄리엣이라는 '역할'이 존재하며, 이 역할들이 연극을 잘 이끌어갈 것이라고만 생각하면 된다.
객체 지향 설계와 스프링
앞선 예시로 다형성만으로는 OCP와 DIP를 지킬 수 없다는 것을 알았다. SOLID 원칙을 모두 지키는 좋은 객체 지향 프로그래밍을 하기에는 너무 복잡하고 할 일이 많았다. 그래서 이를 해결하기 위해 만든 프레임워크가 바로 스프링이다!
스프링은 다형성 + OCP, DIP를 가능하도록 지원해준다. 이를 위해 DI(Dependency Injection: 의존관계 주입)를 이용하고 DI 컨테이너를 제공한다.
결론적으로 스프링을 사용하면 클라이언트 코드이 변경 없이 기능을 확장할 수 있어 쉽게 부품을 교체하듯이 개발할 수 있다.
이 포스트는 인프런의 [스프링 핵심 원리 - 기본편] (김영한) 강의를 수강하고 정리한 글입니다.
'Java > [스프링 핵심 원리]' 카테고리의 다른 글
[스프링 핵심 원리] 컴포넌트 스캔(@ComponentScan) (0) | 2021.12.12 |
---|---|
[스프링 핵심 원리] 싱글톤 컨테이너와 @Configuration (0) | 2021.12.11 |
[스프링 핵심 원리] 스프링 설정 파일 알아보기 - BeanFactory와 ApplicationContext, BeanDefinition (0) | 2021.12.08 |
[스프링 핵심 원리] 스프링 컨테이너와 스프링 빈 (0) | 2021.12.07 |
[스프링 핵심 원리] JAVA 프로젝트에 OOP 원리 적용하기 (SRP, OCP, DIP) (0) | 2021.12.05 |
댓글