본문 바로가기
Spring/스프링 핵심 원리 기본편

스프링 핵심 원리 기본편 - SOLID란 무엇인가?

by latissimus 2022. 8. 30.

인프런 김영한님의 스프링 핵심원리 - 기본편을 학습하고 정리한 내용입니다. 학습 목적의 정리이며 모든 강의 내용을 정리하진 않습니다. 틀린 부분 지적해주시면 감사하겠습니다.

 

해당 강의에서는 순수 자바로 도메인을 설계하고 구현한다. 앞에 나올 SOLID 원칙을 지키면서 순수 자바로 구현하면, 자연스레 스프링의 원리에 다가가게 된다고 한다. 우선 SOLID 원칙이 무엇인지 알아보자.

역할과 구현 : 운전자 - 자동차

SOLID를 알아보기전에 자동차 역할과 구현에 대해 먼저 공부하면 이해를 도울 수 있다.

스프링 핵심원리 - 기본편 : 영한님 강의자료

위와 같이 자동차의 역할과 그의 구현으로 분리해보자.

 

K3, 아반떼, 테슬라 모델3는 자동차라는 역할을 가지지만 구체적인 구현방식은 제각각 다르다. 어떤 연료를 쓰는지, 어떤 방식으로 작동하는지 디테일한 부품들이 무엇으로 구성되었는지 전부 다른 것이다. 하지만 신기하게도 운전자는 휘발유 자동차가 어떻게 피스톤을 움직이는지 알 필요 없다. 심지어 연료가 전기인지 기름인지조차 알 필요가 없다. 운전만 할 줄(자동차의 역할을) 안다면, 자동차의 작동원리나 내부 구조을 몰라도 3가지 차를 모두 운전을 할 수 있다. 

 

그게 가능한 이유는 운전자는 자동차의 역할에 대해 알고 있기 때문이다. 우리는 약속을 통해 엑셀을 밟으면 앞으로 나가고, 브레이크를 밟으면 멈추는 것을 알고 있다.

 

여기서 갑자기 테슬라에서 테슬라 모델 100이라는 차를 출시했다고 하자. 테슬라 모델 100은 자동차 역할에 맞게 만들어졌다. 운전자는 마찬가지로 테슬라 모델100의 내부 구조를 모른다. 하지만 운전자는 해당 차를 운전할 수 있다.

 

이것이 핵심이다. 이와같이 역할과 구현을 분리하면, 운전자(클라이언트)는 자동차의 역할(인터페이스)에만 의존하고, 내부구조(자동차의 구현 내용)을 전혀 몰라도 운전을 할 수 있다. 객체지향 프로그래밍에 적용하면, 새로운 기능(새 자동차)이 추가되어도 클라이언트(운전자)는 변경되지 않는 것이다.

 

이말은 즉슨, 새로운 기능이 추가되어도 기존의 코드와 클라이언트 코드를 수정할 필요가 없게 된다. 그냥 추가하고 싶은 기능에 대한 코드를 추가하면 끝인 것이다. 객체지향 프로그래밍의 큰 장점이다. 이를 바탕으로 SOLID를 이해해보자.

이 예시는 객체지향 프로그래밍의 주요 특징들을 잘 나타낸다. 예시로 운전자가 보내는 동일한 메시지(ex.핸들 조작, 엑셀 밟기...)에 다양한 자동차가 조작되므로 다형성과 연관이 있다. 캡슐화가 되어있기 때문에 자동차의 내부 구조를 모른체 메시지를 보내 구현체를 활용(운전)한다. 
 

SOLID란?

클린코드 책으로 유명한 로버트 마틴이 정리한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙이다. 그것을 마이클 패더스가 머리를 따서 SOLID라고 부르기 시작했다.

 

1. 단일 책임 원칙 (SRP, Single Responsibility Principle)

한 클래스는 하나의 책임만 가져야 한다.

하나의 책임이라는 것이 모호할 수 있는데, 문맥과 상황에 따라 다르다. 요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.

 

영한님의 예시 : 단일 jsp 파일에 모든 프로젝트 로직이 들어있는 경우가 있었다고 한다. 비즈니스 로직, view 관련 로직등이 뒤죽박죽 섞인 엄청나게 거대한 파일을 상상해보자. 해당 파일에 만약 기능을 추가한다면, 어디를 수정해야할지 알 수 있을까? 그리고 side effect를 예상할 수 있을까?

 

객체를 책임별로 명확하게 나누면 어디를 변경할지 알 수 있을 것이다. 이전의 자동차에서 특정 자동차에 기능을 추가하고 싶다면, 해당 자동차의 구현만 변경하면 된다. 새로운 자동차를 추가하고 싶으면, 해당 자동차의 구현하는 클래스를 추가만 하면 된다.

 

2. 개방-폐쇄 원칙 (OCP, Open/Closed Principle)

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

변경에는 닫혀있고, 확장에는 열려 있다는게 무슨 의미일까? 기존 코드를 수정하지 않고, 추가만 하여 확장할 수 있음을 말한다.  인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현만 하면 확장되는 것이다.

 

아까 자동차의 경우를 생각하면 쉽다. 자동차 역할(인터페이스)에 맞는 차를 구현하기만 하면 확장이 되므로 확장에 열려있다. 그리고 기존 코드를 수정할 필요가 없으므로 변경에는 닫혀있다.

 

3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)

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

특정 인터페이스를 구현하는 구현체가 인터페이스의 의도에 맞게 구현되는 경우를 말한다.

 

당연한 얘기지만, 자동차는 엑셀을 밟으면 속도를 높여서 차가 앞으로 나간다. 그런데 아까 예시의 K3 자동차를 엑셀을 밟으면 속도가 줄어들도록 구현했다고 해보자. 이 경우가 리스코프 치환원칙을 위배한 것이다. 상위 타입(자동차의 역할)의 의도(엑셀 밟으면 속도 증가)를 반영하지 못했기 때문이다.

 

4. ISP 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)

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

다음과 같이 인터페이스를 인터페이스로 나눈다고 생각해보자.

  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리

분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않는다. 인터페이스가 명확해지고, 대체 가능성이 높아진다.

 

5. 의존관계 역전 원칙 (DIP, Dependency Inversion Principle)

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

쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻이다. 객체의 역할(Role)에 의존하게 해야 한다는 것이다. 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하게되면 변경이 매우 어려워진다. 참고로 의존성 주입은 이 원칙을 따르는 방법 중 하나다. 

 

이것도 아까의 운전자-자동차로 모두 설명이 된다. 운전자는 추상화된 인터페이스(자동차의 역할)를 의존하게 된다. 하지만 아무 문제 없이 구현체들을 활용할 수 있다.

 

마무리

Spring 국비과정을 들을때, 이게 뭐하는 건지도 모르겠는데 포기는 못하겠고, 뻘뻘거리며 개념들을 암기하던 기억이 난다. 당시 Spring 동작 원리나 객체지향의 특징이 뭘 의미하는지 모르고 암기만 했었는데, 그 당시 이 강의를 수강했으면 어땠을까 싶다. Spring과 객체지향 프로그래밍을 공부하려는 분들은 들어보면 좋을 것 같다.

 

어느정도 객체지향을 이해하고, 다시들으니 감회가 새롭다. 강의의 다음 챕터에서 이것을 주문-할인 도메인으로 직접 코드를 치며 실습한다. 재밌다.

 

 

 

 

댓글