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

6216 단어 JavaSpringJava

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이 오픈소스 프로젝트라는 것이 다행이다 싶은 순간입니다!

좋은 웹페이지 즐겨찾기