[혼자서는 SOLID를 지킬 수 없다] Java 객체 지향, 그리고 객체 지향의 수호자 Spring

Jake·2022년 3월 24일
0

Java

목록 보기
3/8
post-thumbnail
post-custom-banner

Spring이 어떻게 객체 지향 프로그래밍을 지원하는지 알아보고자 합니다.
김영한 님의 인프런 강의를 토대로 정리했습니다.

1. 객체 지향의 핵심 원칙 (SOLID)

객체 지향의 핵심 원칙인 SOLID는 모두에게 익숙하리라 생각합니다.

1_ 단일 책임 원칙 (Single Responsibility Principle)

SRP는 주어진 메서드/클래스/컴포넌트를 변경할 이유가 하나여야 한다는 원칙이다
(출처: 클린코드)

우리가 객체, 클래스, 컴포넌트에게 바라는 것은 '하나', 즉 단일한 것이어야 합니다.
이 때 '바라는 것'의 의미는 '기대하는 역할', 혹은 '책임' 이라고도 할 수 있습니다.

  • 책임의 범위는 문맥과 상황에 따라 다르기 때문에, 이를 잘 조율하는 것이 객체 지향의 묘미입니다.
  • 중요한 기준은 변경입니다. 변경이 있을 때 파급효과가 적을 수록 단일 책임 원칙을 잘 따른 것이라고 할 수 있습니다.

다음과 같은 상황을 가정해 봅시다.

class Shef {
	void cook() {//선주문 선조리};
    void serve() {//선주문 선서빙};
}
  • 위와 같은 예시에서 Shef 클래스는 2개의 책임을 지게 됩니다.
    • 조리의 책임
    • 서빙의 책임
  • 조리 정책을 변경할 경우(1) 서빙 정책을 변경할 경우(2) 모두 Shef 클래스를 변경해야 합니다.
    즉, 주어진 클래스를 변경할 이유가 둘 이상이 됨으로 인해 단일 책임 원칙을 위반하는 것입니다. 따라서 Shef 클래스는 조리에 대한 책임만 지도록 하고 서빙은 Server 클래스를 따로 만들어 주는 것이 나을 것입니다.

2_ 개방-폐쇄 원칙 (Open/closed Principle)

소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다

다형성과 밀접하게 닿아있는 원칙입니다. 기능의 사용은 인터페이스에 의지하도록 하고, 기능의 실제 구현은 인터페이스를 구현한 클래스에서 수행하도록 개방-폐쇄 원칙을 지키는 코드 작성에 도움이 됩니다.
인터페이스에 대한 내용은 여기에 더 자세히 정리해 두었습니다.

하지만, 분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없는 경우가 발생한다.
이를 해결하기 위해서는 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요한데 이것이 바로 스프링이다.
from 김영한 님의 인프런 강의


3_ 리스코프 치환 원칙 (Liskov substitution principle)

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

  • 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 의미입니다.
  • 단순히 컴파일에 성공한다는 것이 아니라, 클래스가 구현하는 인터페이스의 메서드가
    '인터페이스에서 기대되는 기능'을 수행하는 것을 보장해야 한다는 뜻입니다.

4_ 인터페이스 분리 원칙 (Interface Segregation Principle)

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다


5_ 의존관계 역전 원칙 (Dependency Inversion Principle)

프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다."

하지만 정말 모든 상황에서 이것을 지키는 것이 가능한 것일까? 답은 '혼자서는 불가능하다'입니다.


2. 혼자서는 SOLID를 모두 지킬 수 없다.

위에서도 언급했듯, 혼자서는 객체 지향을 지킬 수는 없습니다.
인터페이스에 의존하는 방식으로 코드를 작성할 수는 있겠지만, 결국 코드를 실행(혹은 컴파일)하기 위해서는 구현체가 필요합니다.

public class Main {
	private MemoryInterface memoryInterface;
    
	public void doSomething() {
    	memoryInterface.someMethod();
    }
}

위처럼 아무리 메서드가 구현 클래스가 아닌 인터페이스에 의존하게 만들어도, 결.국. 우리는 이 코드를 쓰는 것을 피할 수 없습니다.

private MemoryInterface memoryInterface = new MemoryInterfaceImpl();

new가 등장하는 순간, DIP는 끝이 납니다...


3. Spring의 등장

제어의 역전 (IoC, Inversion of Control)

구현체의 생성을 외부에서 관리하도록 하는 것, 이를 통해 DIP를 유지하는 것이 바로 제어의 역전입니다.
이 제어의 역전을 통해 '나(개발자)'는 더 이상 구현을 명시해야 할 필요가 없어졌습니다.
Spring 프레임워크에게 이 일을 맞길 수 있게 되었기 때문입니다.

프레임워크(Framework) vs 라이브러리(Library)
면접 질문을 검색하면 자주 나오는 질문 중 하나인데, 얘기가 나온 김에 정리해보려고 합니다.

  • 프레임워크(Framework)
    주도권이 프레임워크에게 있습니다. '나(개발자)'는 프레임워크가 요구하는 양식에 따라 코드를 작성합니다.
    실행 시점에는 프레임워크가 내가 작성한 코드를 제어합니다.
  • 라이브러리(Library)
    반면에 라이브러리는 내가 내 입맞에 맞춰서 가져다 쓰면 됩니다. 즉, '나(개발자)' 직접 제어의 흐름을 담당합니다.

의존성 주입 (DI, Dependency Injection)

  • IoC로 인해 우리는 Spring에 기댈 수 있게 되었습니다. 클래스 내부에 생성자를 두지 않아도 Spring이 런타임에 외부에서 객체를 생성해서 주입시켜 줍니다.

  • 의존성 주입에는 3가지 방법이 있습니다 (자세한 내용은 여기에서)

    • 생성자 주입(Constructor Injection): 생성자에 @Autowired 메서드를 붙여주면 됩니다.

    • 필드 주입(Field Injection): 필드에 @Autowired 어노테이션을 붙여주면 됩니다.

    • 수정자 주입(Setter Injection): setter 메서드에 @Autowired 어노테이션을 붙여주면 됩니다.

  • 여기서 Spring은 객체를 싱글톤으로 관리해줍니다 (싱글톤에 대한 내용은 여기에서)

이처럼 스프링 덕분에 자바 진영은 추운 겨울을 이겨내고 Spring을 맞이할 수 있었습니다.
Spring이 오픈소스 프로젝트라는 것이 다행이다 싶은 순간입니다!

profile
Java/Spring Back-End Developer
post-custom-banner

0개의 댓글