Intro

  • 이전 포스트에서 객체지향을 극대화 하여 유연하고 확장성 높은 코드를 작성했습니다.
  • 이번 포스트에서는 이전 포스트에서 개선해온 결과를 객체지향 기술의 여러 가지 이론을 통해 설명할 예정입니다.

개방 폐쇄 원칙 (OCP, Open-Closed Pridiple)

  • 객체지향 5대원칙 SOLID 중 하나 입니다.
  • ‘클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다’ 는 이론입니다.
  • 인터페이스를 통해 제공되는 확장 포인트는 확장을 위해 개방돼있는 반면, 인터페이스를 이용하는 클래스는 자신의 변화가 불필요하게 일어나지 않돌고 굳게 폐쇄돼습니다.
  • 즉, 이전 포스트에서 개선한 코드를 예로 들면, Juliet의 확장은 개방돼있는 반면, Romeo의 변화는 폐쇄돼있습니다.
  • 결과적으로, 이전 포스트에서 개선한 코드는 OCP를 잘 지키는 코드입니다.

높은 응집도란?

  • 응집도가 높다는 것은 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 뜻입니다.
  • 한 클래스 내에 책임이 여러개 있다면 이 책임들을 나눔으로써 응집도를 높일 수 있습니다.

낮은 결합도란?

  • 결합도란 ‘하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에서 변화를 요구하는 정도’ 입니다.
  • 즉, 낮은 결합도란 하나의 오브젝트의 변경이 다른 오브젝트로 전파되는 정도가 낮은 것 입니다.
  • 구현체를 참조하는 주체를 인터페이스로 구성하면 결합도를 낮출 수 있습니다.

코드 변경

  • 이전 포스트에서 작성했던 Director 클래스의 테스트 관심사를 새로운 ActingTest 클래스를 만들어 분리 시켰습니다.

ActingTest.java

public class ActingTest {

    public static void main(String[] args) {
        Director director = new Director();
        director.getRomeo().acting();
    }

}

Director.java

public class Director {

    public Romeo getRomeo() {
        return new Romeo(getJuliet1());
    }

    public Juliet getJuliet1() {
        return new Juliet1();
    }

}
  • 나머지 코드는 이전 포스트에서 작성한 코드와 동일합니다.

팩토리?

  • 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 역할을 하는 클래스를 팩토리 라고 부릅니다.
  • 위의 예제에서 Director 클래스가 바로 팩토리 입니다.

제어의 역전이란? (IoC, Inversion of Control)

IoC 개념

  • 프로그램의 제어 흐름을 거꾸로 뒤집는 것을 의미합니다.
  • 즉, 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않습니다. 당연히 생성하지도 않습니다.
  • 지금까지 작성 해왔던 예제 코드에도 제어의 역전 개념이 들어가 있습니다.
    • Romeo 자신도 팩토리(Director)에 의해 수동적으로 만들어 지고, Romeo 자신이 사용할 오브젝트(Juliet)도 팩토리(Director)가 공급해주는 것을 수동적으로 사용합니다.
    • 즉, Romeo와 Juliet의 구현체를 생성하는 책임을 Director가 맡고 있는 상황인 것입니다.
    • 바로 이러한 상황을 가리켜 제어의 역전(IoC)라고 부릅니다.

그렇다면 이 책에서는 왜 리팩토링 과정을 거쳐가며 IoC를 설명한 것일까요?

  • 그것은 바로 스프링이 IoC를 모든 기능의 기초가 되는 기반기술로 삼고 있으며, IoC를 극한까지 적용하고 있는 프레임워크이기 때문입니다.

순수 자바 코드를 스프링 코드로 변경하기

ActingTest.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ActingTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Director.class);
        Director director = context.getBean("director", Director.class);
        director.getRomeo().acting();
    }

}

Director.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Director {

    @Bean
    public Romeo getRomeo() {
        return new Romeo(getJuliet1());
    }

    @Bean
    public Juliet getJuliet1() {
        return new Juliet1();
    }

}
  • 나머지 코드는 이전 포스트에서 작성한 코드와 동일합니다.
  • 기존의 오브젝트 팩토리(Director)로 구성되어 있던 코드를 스프링의 애플리케이션 컨텍스트로 변경하였습니다.
  • 스프링의 기능을 적용했지만, 이전 코드보다 사실 다소 지저분해졌습니다.
    • 그렇다면 왜 굳이 저런식으로 코드를 작성하는 것일까요?
    • 스프링에서는 이전에 구현해왔던 방식으로는 얻을 수 없는 방대한 기능과 활용 방법을 제공해 준다고 합니다. (이 내용에 대해서는 차차 알아보도록 하겠습니다.)

스프링 IoC의 용어 정리

  • bean
    • 스프링이 IoC 방식으로 관리하는 오브젝트 (스프링이 직접 생성과 제어를 담당하는 오브젝트)
  • bean factory
    • 스프링의 IoC를 담당하는 핵심 컨테이너 입니다.
    • bean 생성과 제어 기능을 담당합니다.
    • bean factory의 기능을 확장시킨 것이 바로 application context 입니다.
  • application context
    • bean factory를 확장한 IoC 컨테이너 입니다.
    • bean factory의 bean 생성과 제어 기능에 스프링이 제공하는 부가 서비스를 추가로 제공합니다.
  • 설정정보/설정 메타정보
    • application context가 IoC를 적용하기 위해 사용하는 메타정보를 말합니다.
    • application의 전체 그림이 그려진 청사진 이라고도 합니다.
  • 스프링 컨테이너 또는 IoC 컨테이너
    • IoC 방식으로 bean을 관리한다는 의미에서 application context나 bean factory를 컨테이너 또는 IoC 컨테이너라고 합니다.

오브젝트 팩토리 vs 애플리케이션 컨텍스트

  • 두 방식의 테스트 결과만 놓고 보면 차이가 없는 것처럼 보입니다.
  • 그렇다면 두 방식은 어떤 차이점이 있는 것일까요?

오브젝트 팩토리 코드

public class ActingTest {

    public static void main(String[] args) {
        Director director = new Director();
        Romeo romeo1 = director.getRomeo();
        Romeo romeo2 = director.getRomeo();

        System.out.println(romeo1);
        System.out.println(romeo2);
    }

}
  • 출력 결과

결과1

애플리케이션 컨텍스트 코드

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ActingTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Director.class);
        Director director = context.getBean("director", Director.class);

        Romeo romeo1 = director.getRomeo();
        Romeo romeo2 = director.getRomeo();

        System.out.println(romeo1);
        System.out.println(romeo2);
    }

}
  • 출력 결과

결과2

애플리케이션 컨텍스트의 bean은 싱글톤 으로 관리된다.

왜 스프링은 싱글톤으로 bean을 관리할까?

  • 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버 환경이기 때문입니다.
  • 클라이언트의 요청이 들어올 때마다 매번 오브젝트를 생성하면 점차 부하가 심해져 서버가 감당하기 힘든 상황이 올 수 있기 때문입니다.

일반적인 싱글톤 패턴의 문제점은?

  • private 생성자를 갖고 있기 때문에 상속할 수 없습니다.
    • 상속과 상속을 이용한 다형성을 적용할 수 없습니다.
  • 싱글톤은 테스트하기가 힘듭니다.
    • 싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 Mock 오브젝트 등으로 대체하기가 힘듭니다.
  • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직 하지 못할 수 있습니다.
    • 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델입니다.

싱글톤 레지스트리란?

  • 스프링의 싱글톤 레지스트리에서는 자바의 기본적인 싱글톤 패턴의 구현방식에 여러가지 단점을 보완하기 위해 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공합니다.
  • 일반적인 싱글톤 패턴과는 달리 스프링이 지지하는 객체지향적인 설계방식과 원칙을 적용하는데 제약이 없습니다.
  • 즉, 스프링이 bean을 싱글톤으로 만드는 것은 결국 오브젝트의 생성 방법을 제어하는 IoC 컨테이너로서의 역할입니다.
    • 그러므로, 애플리케이션 코드 관점에서 싱글톤에 대해 생각할 필요가 없습니다.

의존관계 주입 (DI, Dependency Injection)

의존관게란?

  • 의존한다는 것은 의존대상이 변하면 그것에 의존하고 있던 것에도 영향을 미친다는 것 입니다.
  • 두 개의 클래스가 의존관계에 있다고 말할 때는 항상 방향성을 부여해줘야 합니다.

의존관계 주입이란?

  • 구체적인 의존 오브젝트(구현체)와 그것을 사용할 주체(client)를 런타임 시에 연결해주는 작업을 말합니다.
    • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않습니다. 그러기 위해서는 인터페이스에만 의존하고 있어야 합니다.
    • 런타임 시점의 의존관계는 컨테이너와 같은 제3의 존재가 결정합니다.
    • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 주입해줌으로써 만들어 집니다.
  • 의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재(스프링 컨테이너)가 있다는 것입니다.

결과

  • 1장을 통해 스프링에서 사용하고 있는 다양한 방법과 패턴, 원칙, IoC/DI에 대해서 알아보았습니다.

참고

  • 에이콘/토비의 스프링 3.1 vol1/이일민 지음

댓글남기기