Spring은 Java 기반의 Framework이다.
따라서 객체 지향 언어인 Java에 대해서 먼저 알아야 할 필요가 있다.

객체 지향 프로그래밍?

객체 지향 프로그래밍은 절차 지향 프로그래밍의 반댓말로,
컴퓨터의 프로그램을 명령어의 집합으로 보는 시각에서 벗어나 여러 개의 객체 들의 모임으로 파악하는 것이다.

객체 는 내부에 자료형을 갖는 Field(상태)와 함수의 형태인 Method(행위)를 갖는다.
기존 절차지향 프로그래밍에서 흩어져 있던 자료형과 함수를 하나의 Class로 묶어 버린 것이다.
이렇게 되면, 객체 간의 독립성 이 뚜렷해지며, 중복되는 코드가 현저히 줄어들어 유지 및 보수에 용이할 것이다!

각각의 객체는 메시지를 주고받고 데이터를 처리하는 협력 이 가능하다.
매우 유연하고 변경이 용이하게 만들어져, 대규모 SW 개발에 많이 사용된다.
이 유연하고 변경이 용이한 객체 지향의 핵심이 바로 다형성!

객체 지향 프로그래밍의 특징

객체 지향 프로그래밍에는 4가지의 큰 특징이 존재한다.

  1. 추상화(Abstraction)
  2. 캡슐화(Encapsulation)
  3. 상속(Inheritance)
  4. 다형성(Polymorphism)

아래에서 자세하게 알아보자.

추상화(Abstraction)

객체들이 공통적으로 필요로 하는 속성, 동작을 하나로 추출해내는 작업이다.
설계의 유연함은 구현체가 아닌 추상적 개념에 의존함에 따라 나오게 되는것이다.

ex) 자동차라는 큰 추상화 개념이 있다고 가정하자.

  • 자동차는 모두 엑셀, 브레이크, 클락션, 등등의 공통 특성이 있다.
    • 새 자동차를 만드려고 한다면, 이 추상화 클래스를 구현하기만 하면 된다.
    • 상속받아서 존재하는 특성을 Override하고, 새 기능이나 특성을 만들면 된다.
    • 굉장히 유연한 설계가 가능한 것이다!

캡슐화(Encapsulation)

정보 은닉을 통해 응집도를 높이고, 결합도를 낮추는 것.
한 곳에서 변화가 일어나도 다른 곳에 미치는 Side effect를 최소화한다.

위의 말은 곳, 객체 각각의 독립성을 최대한 높이기 위한 설계라고 볼 수 있다.
객체 내의 요소들을 접근 제한을 두어 결합도를 떨어트리고 자체 응집도를 높인다.
이렇게 떨어진 다른 객체와의 결합도를 통해 요구사항 변경에 유연한 대처가 가능하다.

사용자는 이에 따라 객체 내부의 구조를 전혀 알 필요가 없으며,
제공하는 필드와 메소드만 이용을 할 수 있다.
즉, 의도치 않은 동작 오류를 방지하며 유지 및 보수 효율을 높일 수 있다.

상속(Inheritance)

여러 개체들이 지닌 공통 특성을 부각시켜 하나의 개념으로 성립하는 과정.
일반화라고도 하며, 추상화와 상당히 유사한 면모가 있는 개념이다.

엄밀히 말하자면, 자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이다.
상속을 받은 자식 클래스 또한, 같이 캡슐화가 되어 외부와는 무관하게 개발을 진행할 수 있다.

상속을 활용하면 상위 클래스의 구현을 활용함으로써, 코드 재사용이 용이해진다.
그러나 상속을 토한 재사용은 명백한 단점이 존재한다.

  1. 부모 클래스의 변경이 불편해진다.
    • 상속받은 자식이 많을 경우, 자식들이 모두 부모의 변경에 영향을 받는다.
  2. 불필요한 클래스가 증가한다.
  3. 잘못된 상속을 사용할 수도 있다.

다형성(Polymorphism)

다형성은 세상을 역할구현 으로 구분한다.

image

위의 예시에서, 운전자는 자동차를 운전할 줄만 알면
모든 차를 운전할 수가 있다. 즉, 역할만 알고 있다면 구현체가 바뀌어도 사용에 전혀 문제가 없다.

즉, 우리는 Client에게 영향을 주지 않고 새 제품을 출시할 수가 있다!
따라서 아래와 같은 장점이 생긴다.

  • Clinet는 대상의 역할(Interface)만 알면 된다.
  • Clinet는 굳이 내부 구조를 알 필요가 없다.
  • Client는 내부 구조가 변경되어도 영향을 받지 않는다.
  • Clinet는 대상 자체를 변경해도 영향을 받지 않는다.

다형성의 본질은 Client의 변경 없이,
Instance를 실행 시점에 유연하게 변경할 수 있다는 점이다.

좋은 OOP를 위한 5가지 원칙(SOLID)

  • SRP : 단일 책임 원칙
    • 한 클래스는 하나의 책임만 갖는다.
    • 하나의 책임이라는 것은 매우 모호하다.
    • 이 때, 중요한 기준은 변경했을 때 파급효과가 적은지이다.
      • SRP를 잘 따랐다면, 파급효과가 적을 것!
  • OCP : 개방-폐쇄 원칙
    • SW는 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다.
    • OCP가 깨진다는 것은, 다형성을 이용한 코드 변경 없는 확장이 불가능 하다는 것이다.
    • ex) MemberService의 OCP
      image
      • 이 경우엔, 다형성을 활용했으나 OCP가 깨져버린 경우이다.
      • 이를 해결하기 위해서는 별도의 조립, 설정자가 필요하다!(Spring Container)
  • LSP : 리스코프 치환 원칙
    • 객체들은 프로그램의 정확성을 유지하며 하위 타입의 Instance로 바꿀 수 있어야 한다.
    • ex) 자동차의 엑셀은 앞으로 가는 기능, 뒤로 가게 구현하면 LSP 위반!
  • ISP : 인터페이스 분리 원칙
    • 특정 Client를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다!
    • ex) 자동차 인터페이스 => 운전 부와 정비 부로 분리
    • ex) 사용자 클라이언트 => 운전자 클라이언트, 정비사 클라이언트로 분리.
    • 분리를 하면, 정비 인터페이스가 변해도 운전자는 영향을 받지 않는다.
  • DIP : 의존관계 역전 원칙

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

    • 즉, 구현 클래스가 아닌 인터페이스에 의존하도록 설계 해야한다.
      • 역할에 의존함으로써, 유연하게 구현체를 변경하도록 할 수 있다.

OCP와 DIP는 객체 지향 프로그래밍에 있어서 매우 중요하다!
그리고, 다형성만으로는 OCP와 DIP를 지키기 힘들다!

우리는 왜 Spring을 사용하는가?

Spring은 아래와 같은 기술로 다형성 + OCP, DIP를 가능하도록 한다.

  • DI(Dependency Injection) : 의존성 주입
  • DI Container : 프로그램의 제어권을 갖고, 의존성을 주입해주는 곳
  • IOC(Inversion of Control) : 제어의 역전
    • DI Container에 의해서 구현 가능한 기술.
    • Client 코드에서는 더 이상의 변경이 일어나지 않도록 해준다.

위의 기술로 인해, Client 코드 변경 없이 확장이 가능하다.
즉, 쉽게 부품만 갈아끼우 듯이 교체를 통한 개발이 가능해진다.