spring

Spring - 좋은 객체 지향 설계란?

에그마요샌드위츼 2025. 3. 18. 14:14

/* 이 글은 김영한님의 강의를 보고 정리하려고 작성한 글입니다.  개인적인 공부를 위해 올리는 글입니다. */

 


스프링의 핵심 

  • 자바 언어 기반의 프레임워크
  • 객체 지향 언어
  • 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임 워크이다.

 

그럼 좋은 객체 지향은 무엇일까? 객체 지향 프로그래밍을 구글에 검색해보면 이렇게 나온다.

객체 지향 프로그래밍(영어: Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나이다. 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 쉽게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 또한 프로그래밍을 더 배우기 쉽게 하고 소프트웨어 개발과 보수를 간편하게 하며, 보다 직관적인 코드 분석을 가능하게 하는 장점이 있다. 프로그램의 객체화 경향은 실제 세계의 모습을 그대로 반영하지 못한다는 비판을 받기도 한다.

 

유연하고 변경이 용이? 

컴퓨터 부품 갈아 끼우듯 컴포넌트를 쉽고 유연하게 변경하여 개발할 수 있는 것을 의미한다.

객체지향에서 이런게 있는데 바로 "다형성" 

 

다형성(Polymorphism)

 

다형성의 의미를 표준 국어사전에서 찾아보면, '같은 종의 생물이면서도 어떤 형태나 형질이 다양하게 나타나는 현상' 을 의미하는데 이것을 프로그래밍에 대입해보면, 같은 이름의 메서드나 연산자를 다른 클래스에서 다르게 구현하는 것이다. 

 

 

다형성의 예시 - 운전자와 자동차

운전자와 자동차로 예를 들어보자면 

내가 자동차를 K3에서 벤츠로 바꿨을 때 나는 운전을 계속 할 수 있다. 자동차가 바뀌어도 새로 운전면허를 딸 필요없이 계속 운전을 할 수 있다. 왜 그럴 수 있을까?

자동차 역할의 인터페이스를 따라서 벤츠, K3, 포르쉐를 구현했을 것이다. 운전자인 나는 자동자 역할만 알고있지만 구현체가 바뀌어도 운전이 가능한 것이다.

 

 

 그럼 자동차 역할을 만들고 구현을 분리한 이유는? 바로 운전자를 위해 그렇게 한 것이다.

나는 자동차의 내부 구조를 알 필요도 없고 기름차에서 전기차가 나와도 운전이 가능하고, 새로운 자동차가 나와도 나(클라이언트)는 새로운 걸 배울 필요가 없이 그냥 운전을 할 수 있다. 

 

새로운 기능이 추가되어도 클라이언트는 새로운 걸 배울 필요가 없다는 것이다. 

자동차의 종류가 바뀌어도 운전자에게는 영향을 주지 않는다. 자동차는 언제나 다른걸로 대체가 가능하다.

이게 바로 유연하고 변경이 용이하다는 뜻이다.

 

역할(인터페이스)과 구현(구현한 객체)

이렇게 구분하게 되면 단순해지고 유연해지고 변경도 편리해진다.

  • 나는 인터페이스만 알면 된다.
  • 나는 내부 구조를 몰라도 된다.
  • 나는 내부 구조가 변경되어도 영향을 받지 않는다.
  • 나는 차가 K3>포르쉐로 바꿔도 영향을 받지 않는다. 

객체를 설계할 때 역할(인터페이스)를 먼저 구현하고 그 역할을 수행하는 객체를 만들면 된다. 핵심은 인터페이스가 먼저이다. 

 

 

다형성의 본질

  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 가능하다.
  • 클라이언트를 변경하지 않고, 서버 구현 기능을 유연하게 변경할 수 있다.
  • 다른 코드들한테 영향을 주지 않고 무한 확장이 가능하다.

인터페이스를 안정적으로 설계하는 게 중요! 이게 깨지면 다 깨진다.

 


좋은 객체 지향 설계의 5가지 원칙(SOLID)

SOLID : 클린코드로 유명한 로버트 마틴이 좋은 객체 지향의 설계에 대해 정리한 것

 

SRP 단일 책임 원칙

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

  • 하나의 책임 = 사실 모호한 개념이다. 클 수도 작을 수도 있다. 적절하게 잘 설계를 해야한다ㅏ.
  • 중요한 기준이 변경이다. 변경이 있을 때 파급이 적으면 단일 책임 원칙을 잘 따른 것이다. 
  • 계층이 잘 나뉘어진 이유는 이 단일 책임 원칙을 따르기 위해서다.
  • 예 : 객체의 생성과 사용을 분리, 변경이 있을 때 하나의 클래스나 하나의 지점만 고치는 것

 

OCP 개방-폐쇄 원칙

확장에는 열려있으나 변경에는 닫혀 있어야 한다.

  • 그럼 어떻게 코드에 변경없이 기능을 추가할까? 말이 안되지 않나?
  • 다형성을 생각해보면 자동차가 K3에서 벤츠로 바뀌어도 운전자는 운전을 할 수 있다.
  • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현하면 된다. 
  • 예 : 자동차 인터페이스를 기준으로 전기차, K3, 포르쉐, 아반떼 등등을 새로 만드는 것

문제점 

  • 구현 객체를 변경하려면 클라이언트 코드를 변경해야 된다. > 변경에는 닫혀있어야 한다면서??? 
  • 분명 다형성을 사용했지만 적용을 하려니까 OCP가 깨진다.
  • 이 문제는 어떻게 해야할까?
  • 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다 -> 전부 Spring이 해주는 일!

 

LSP 리스코프 치환 원칙

어떤 인터페이스가 있고 그 구현체가 있다. 예를 들면 자동차 인터페이스에서의 구현체가 엑셀은 무조건 앞으로 가야 되는 규약이 있는데

  • `엑셀을 밟는다` -> `-10` 차가 뒤로감 (X)  -> LSP 원칙 위반
  • `엑셀을 밟는다` -> `+10` 차가 앞으로 감  -> LSP 원칙 준수!

 

ISP 인터페이스 분리 원칙

  • 자동차라는 인터페이스에 운전 기능, 정비 기능, 등등 여러 기능이 있으니까 기능에 따라 인터페이스를 분리하는 것이다. 
  • 이러면 인터페이스가 명확해지고 대체 가능성이 높아진다.

 

DIP 의존관계 역전 원칙

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

  • 이게 또 무슨소리일까?
  • 클라이언트 코드가 구현 클래스를 바라보지 말고 `인터페이스`를 바라봐라 이런 소리이다.
  • 자동차 역할에만 알면 되지 K3, 벤츠 이런 자세한 거 까진 알 필요가 없다는 것이다.

 

정리해보면

객체 지향의 핵심은 다형성이고, 스프링은 이 객체지향의 장점을 살리는 프레임 워크이다.
하지만 객체 지향의 원리와 다형성만으로는 쉽게 부품 갈아 끼우듯 개발할 수 없다. 
다형성만으로는 구현 객체를 변경할 때 클라이언트 코드도 변경이 되어야 한다. OCP, DIP를 지킬 수 없다.
DIP, OCP를 지키려면 뭔가가 더 필요하구나!

 

하지만 스프링은 다형성 + OCP,DIP를 가능하게 지원을 해줘서 클라이언트 코드변경 없이 기능을 확장할 수 있게 해준다.

  • DI(Dependency Injection) : 의존 관계, 의존성 주입
  • DI 컨테이너 제공