1. 객체지향 설계와 스프링

2021. 11. 22. 15:18Spring/Boot

1. 스프링의 탄생 배경

초기의 자바 웹 개발은 EJB를 통해 개발했다. 당시에 분산 시스템, 트랜잭션 등과 같은 기능을 지원하고 ORM, 객체지향적 설계 등이 적용된 통합적인 프로그램이었다. 하지만 개발자가 사용하기가 상당히 어렵고 느리다는 단점이 있었다. 그래서 로드 존슨이라는 개발자가 EJB를 비판하며 책을 내는데 이것이 스프링의 시작이 된다. 스프링이라는 이름도 EJB라는 겨울에서 봄(Spring)이 왔다는 뜻으로 붙여진 이름이다.

2. 스프링의 핵심 개념

기술을 배울 때 가장 중요한 것은 그 기술이 어떤 문제를 해결하기 위해 탄생했는지, 핵심 원리가 무엇인지 파악하는 것이다. 그 외의 것들은 부가적인 부분인 것이다. 이를 깨우치면 아무리 기능이 많아지더라도 빠르게 기술을 습득할 수 있다.

스프링은 자바 기반의 프레임워크이다. 따라서 객체 지향적인 특징을 가지게 된다. 그렇기 때문에 객체 지향의 장점을 살려내도록 개발자를 도와준다. 따라서 자바와 객체 지향적인 사고가 바탕이 된다면 스프링의 장점을 극대화할 수 있다.

3. 좋은 객체 지향 프로그래밍

그렇다면 좋은 객체지향 프로그래밍을 하려면 어떻게 해야 할까? 이 질문에 답을 하기에 앞서 객체 지향의 기본을 알아보도록 하자.

객체 지향 프로그래밍(Object Oriented Programming, OOP)은 세상을, 즉 사물과 생물, 개념 등을 객체로 바라보고 이를 객체들의 유기적인 모임으로 파악하는 프로그래밍 방법이다. 각각의 객체는 메시지를 통해 데이터를 주고받고 처리한다. 또한 프로그램을 유연하고 변경하기 쉽게 만들도록 설계하기 때문에 대규모 프로젝트에서 많이 채택하는 방법이다. 유연하다는 것은 프로그램을 레고 조립하듯이 개발할 수 있다는 것을 의미한다. 객체 지향의 특징은 다음과 같다.

 

  • 캡슐화(Encapsulation): 외부에서 필요하지 않은 부분은 숨기는 특징이다.
  • 상속(Extend): 객체들은 부모와 자식과 같은 관계를 가질 수 있다. 이를 통해 중복을 제거할 수 있다.
  • 추상화(Abstract): 객체의 중요한 특징만을 남기고 공통된 특징을 뽑아내는 것을 말한다.
  • 다형성(Polymorphism): 객체 지향의 특징 중 가장 중요한 특징으로, 같은 타입이라면 여러 형태를 가질 수 있는 특징이다. 예를 들어 "나는 차를 가지고 있다" 라는 문장이 있다. 여기서 치는 아반떼, 말리부, 제네시스 등으로 교체할 수 있다. 여기서 중요한 것은 운전자(클라이언트)는 차가 어떻게 설계되었는지 알 필요가 없다는 것이다. 운전자는 차를 운전하는 방법만 알고 있으면 되는 것이다. 차의 기능이 변경되거나 차의 종류가 바뀌더라도 말이다. 이것이 가능한 이유는 차를 만들 때 모두가 같은 인터페이스를 사용하기 때문이다. 따라서 객체 지향적으로 설계할 때는 역할과 구현을 명확히 구분해야 한다. 먼저 역할을 인터페이스로 작성하고 상세한 부분은 클래스로 구현해야 한다. 그렇기 때문에 인터페이스를 안정적으로 설계하는 것이 매우 중요하다. 나중에 인터페이스를 고치게 된다면 관련된 코드를 전부 고쳐야 하기 때문이다. 자바에서는 이러한 다형성을 인터페이스(interface)를 통해 주로 구현한다. 그 외에도 오버로딩, 오버라이딩과 같은 방법이 있다.
💡 다형성의 본질은 클라이언트의 코드를 변경하지 않고도 구현 기능을 유연하게 변경할 수 있다는 것이다.

 

이러한 특징을 잘 살려서 객체 지향적으로 개발하려면 어떻게 해야 할까? 이를 위해 클린 코드로 잘 알려진 로버트 마틴이 제안한 SOLID 원칙이 있다.

  • SRP: 단일 책임 원리
    • 한 클래스는 하나의 책임만 가져야 한다.
    • 중요한 기준은 변경이다. 변경 시에 파급 효과가 크다면 SRP를 위반한 것이고, 적거나 없다면 SRP를 준수한 설계이다.
  • OCP: 개방-폐쇄 원칙
    • 확장에는 열려있고 변경에는 폐쇄적이어야 한다.
    • 위에서 다형성을 설명할 때와 같이 운전자는 바뀌지 않고 자동차만 추가하여 변경하도록 설계하는 것이다. 하지만 이런 경우에도 OCP를 완전히 지킬 수 없다. 아래처럼 코드를 변경해야 한다. 이러한 문제를 해결하기 위해서는 외부에서 객체를 생성하고 연관관계를 맺어주는 것이 필요하다. 후에 이 문제를 해결해볼 것이다.
    • class Example { // Drivable man = new Avante(); Drivable man = new Genesis(); }
  • LSP: 리스코프 치환 원칙
    • 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입으로 변경할 수 있어야 하는 원칙이다.
    • 즉, 하위 타입은 인터페이스에서 설계한 원칙을 지켜야 한다는 것이다.
    • 예를 들어 자동차의 악셀 기능을 앞으로 가도록 설계했다면 이를 구현한 구현체(아반떼, 제네시스 등)도 앞으로 가도록 기능을 구현해야 한다.
  • ISP: 인터페이스 분리 원칙
    • 특정 클라이언트를 위한 인터페이스를 한 개보다 여러 개로 설계하는 것이 좋다는 원칙이다.
    • 자동차 인터페이스를 운전 인터페이스, 정비 인터페이스 등으로 분리하는 것과 같다.
  • DIP: 의존관계 역전 원칙
    • 프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다는 원칙이다.
    • 즉, 인터페이스에 의존해야지 구현체에 의존하면 안된다는 것이다.
    • class Example { Drivable man; }
    • 위에서 설명한 자동차 예제는 DIP를 위반한 설계이다. DIP를 준수하면 다음과 같이 설계될 것이다. 코드만 본다면 무슨 소리지? 라고 생각할 수 있다. 인터페이스는 직접 인스턴스를 생성할 수 없기 때문이다. 이러한 OCP, DIP를 지키면서 설계하기 위해 의존관계 주입(Dependency Injection, DI)이라는 방법이 개발되었다. 이는 다른 글에서 설명하도록 하겠다.

정리

스프링은 기존의 험난한 웹 개발 방식에서 개발자들을 위해 객체 지향적으로 설계할 수 있도록 등장한 프레임워크이다. 스프링을 잘 활용하기 위해서는 객체 지향 프로그래밍에 대한 이해가 필요하며 객체 지향의 특징과 설계 원칙을 잘 알고 설계해야 한다.