💡 `OOP(객체 지향 프로그래밍)` 이란 **문제를 여러 개의 객체 단위로 나눠 작업하는 방식**으로, 객체들이 서로 유기적으로 상호작용하는 프로그래밍 이론이다. 대표적으로 Java와 C#이 객체 지향 프로그래밍 언어이다.Spring의 큰 장점 중 하나는 자바의 장점인 OOP(객체지향프로그래밍)을 할 수 있다는 것이다.
다형성이란 역할과 구현의 분리를 말한다.
다형성을 고려하고 객체를 설계할 때 Interface(역할)을 먼저 부여한 후 그 역할을 수행하는 **Implementation(구현체)**를 만들게 된다.
이러한 방법으로 설계를 한다면 클라이언트는 **Interface(인터페이스)**을 사용하기 때문에 인터페이스를 구현한 **Implementation(구현체)** 내부에 대해서 알 필요가 없다. 또한 구현체를 변경할때 클라이언트의 코드는 전혀 영향을 받지 않는다는 장점이 있다.
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
MemoryMemberRepository와 JdbcMemberRepository는 MemberRepository인터페이스를 구현한 구현체이다. 현재는 JdbcMemberRepository 구현체를 사용하고 있다.
이처럼 다형성을 활용하면 실행시점에 객체 인스턴스를 유연하게 바꿀 수 있고, 클라이언트 코드 변경 없이 서버의 구현 기능을 변경할 수 있기 때문에 확장에 유용하다.
하지만 순수한 자바로는 SOLID 원칙을 지키지 못한다.
SRPOCPLSPISPDIP로OOP할때 지켜야하는 원칙이다.
단일 책임 원칙 : 한 클래스는 하나의 책임만 져야 한다.
하나의 책임이란 말이 모호하지만, 변경을 기준으로한다.
어떤 기능이 외부 클래스나 라이브러리에 종속적이게 된다면 라이브러리나 외부 클래스가 변경이 된다면 기능에 영향이 있다. 이를 최소화 할 수 있도록 클래스를 설계하는 것이 원칙이다.
개방 폐쇄 원칙 : 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
private MemberRepository memberRepository = new JdbcMemberRepository();
}
이처럼 MemberService (클라이언트) 에서 MemoryMemberRepository(구현체) 에서 확장된 기능이 구현되어 있는 JdbcMemberRepository(구현체)로 교체를 했어도 결국 클라이언트에서 구현체를 바꿔야하기 때문에 순수 자바 만으로 OCP를 순전히 지키기 힘들다.
해결 → Spring을 사용한다면 이 부분을 해결**해 준다.
리스코프 치환 원칙 : 다형성에서 하위 클래스는 인터페이스의 규약을 다 지켜야한다.
같은 인터페이스나 클래스를 상속받는다면 구현체들은 인터페이스 및 클래스의 규칙을 지켜서 개발해야한다.
인터페이스 분리 원칙 : 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나 보다 낫다.
예를 들어 Car라는 인터페이스를 운전 인터페이스, 정비 인터페이스로 분리한다.
그러면 클라이언트도 운전 클라이언트, 정비 클라이언트로 분리가 되는데 이러면 정비 클라이언트가 변해도 운전 클라이언트에 영향을 주지 않는다.
즉 인터페이스가 명확해지고, 대체 가능성이 높아진다.
💡 **한계** → 기능 별로 상세하게 인터페이스를 나누는 것 `ISP`를 잘 따르는 것이지만, 그렇게 된다면 **추상화 비용**이 발생한다. 💡 `**추상화 비용이란?` 성능에 대한 비용**이 아닌 **복잡도에 대한 부분**을 말한다. 추상화가 없다면 그냥 코드를 따라가면 되는데, 추상화가 있다면 추상 인터페이스를 보고 **어떤 구현체가 실제 동작할지 또 추가로 찾아야하는 과정을 거쳐야한다.**즉, 추상화를 한다면 구현체를 갈아끼울 수 있으서 확장성이 늘어나기 때문에 유지보수하기 좋아지는 부분도 있지만 반대로 필요하지 않은 곳 까지 추상화하게 된다면 코드를 유지보수하기 더 어려워 질 수 있다.
좋은 개발자의 역량 중 하나가 이러한 부분을 적절히 잘 선택하는 것이다.
의존관계 역전 원칙 : 구현 클래스에 의존하지 않고 인터페이스를 의존하라
만약 구현 클래스를 의존하면 **OCP를 지키기 어려울 뿐만 아니라 변경이 아주 어려워**진다. 즉 클라이언트가 구현 클래스는 모르고 인터페이스를 의존하여 사용하라는 뜻이다.
public class MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository();
}
여기서 **MemberService(클라이언트)**는 MemberRopository (인터페이스)를 의존하고 있지만 이를 구현한 MemoryMemberRepository(구현체)도 의존하고 있다
해결 → Spring을 사용한다면 이 부분을 해결해 준다.
스프링은 순수 자바의 한계점(
OCP,DIP) 들을 다음 기술로 해결해준다.
DI(Dependency Injection)DI Container
이 기술들로 클라이언트 코드의 변경없이 기능을 확장할 수 있다.
IoC란 Inversion of Control의 줄임말이며, 제어의 역전이라고 한다.메소드나 객체의 호출작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다.
public class MemberService {
// 구현체 의존 DIP 위반
// 구현체 변경을 하려면 클라이언트에서 수정해야 하므로 OCP 위반
private MemberRepository memberRepository = new JdbcMemberRepository();
// private MemberRepository memberRepository = new MemoryMemberRepository();
}
위 코드는 클라이언트가 필요한 서버 구현 객체를 스스로 생성하고, 연결하고, 실행하였다. 즉 구현 객체가 프로그램의 제어 흐름을 스스로 조종하였다.
여기에 IoC를 적용한다면
public class AppConfig {
public MemberRepository memberRepository() {
return new JdbcMemberRepository();
}
public MemberService memberService() {
return new MemberService(memberRepository());
}
}
AppConfig class를 만들어서 JdbcMemberRepository(구현체)를 대신 생성해서 MemberService(클라이언트)에 주입할 수 있도록 만들어주었다.
즉, AppConfig class는 프로그램의 제어를 담당하는 클래스이다.
의존성 주입이라는 뜻으로,
IOC를 구현하기 위해 사용하는 기법 중 하나이다.
public class MemberService {
// 클라이언트는 인터페이스에만 의존한다. DIP 지킴
// 구현체를 변경해도 클라이언트의 변화는 없다. OCP 지킴
private final MemberRepository memberRepository;
//외부에서 의존하는 객체를 주입받는다.
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public String save(String username) {
return memberRepository.save(username);
}
}
이와 같이 의존하는 객체를 외부에서 주입받도록 설계하는 기술이 DI 이다.
💡 IOC를 적용한 `MemberService(클라이언트)`를 보면 `JdbcMemberRepository`를 찾아 볼수 없고 모두 `MemberRepository(인터페이스)`를 의존하고 있다 → **`DIP`를 지키고 있다.** 💡 `JdbcMemberRepository`, `MemoryMemberRepository` 중 둘 중 어떤 구현체를 사용하는 가에 대한 코드가 전혀 없기 때문에 **구현체를 바꾸어도** **클라이언트 코드의 변경이 없다**. → **`OCP`를 지키고 있다.**스프링 프레임워크도 객체에 대한 생성 및 생명주기를 관리할 수 있는 기능을 제공하고 있다. 즉,
IoC 컨테이너 기능을 제공한다.
인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해줍니다.
객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있습니다.
💡 **“컨테이너”**란?
보통 객체의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 역할
💡 **“POJO”** 란?
주로 **특정 자바 모델이나 기능, 프레임워크를 따르지 않는 Java Object를 지칭**한다.
Java Bean 객체가 대표적이다.
감사합니다. 이런 정보를 나눠주셔서 좋아요.