Skip to content

Latest commit

 

History

History
1242 lines (799 loc) · 60.4 KB

스프링 입문을 위한 자바 객체 지향의 원리와 이해.md

File metadata and controls

1242 lines (799 loc) · 60.4 KB

스프링 입문을 위한 자바 객체 지향의 원리와 이해

목차

[001. 사람을 사랑한 기술](# 001. 사람을 사랑한 기술)

[002. 자바와 절차적/구조적 프로그래밍](# 002. 자바와 절차적/구조적 프로그래밍)

[003. 자바와 객체 지향](# 003. 자바와 객체 지향)

[004. 자바가 확장한 객체 지향](# 004. 자바가 확장한 객체 지향)

[005. 객체 지향 설계 5원책 - SOLID](# 005. 객체 지향 설계 5원책 - SOLID)

[006. 스프링이 사랑한 디자인 패턴](# 006. 스프링이 사랑한 디자인 패턴)

[007. 스프링 삼각형과 설정 정보](# 007. 스프링 삼각형과 설정 정보)

[A. 스프링 MVC를 이용한 게시판 구축](# A. 스프링 MVC를 이용한 게시판 구축)

[B. 자바 8 람다와 인터페이스 스펙 변화](# B. 자바 8 람다와 인터페이스 스펙 변화)

001. 사람을 사랑한 기술

1.1. 신기술은 이전 기술의 어깨를 딛고

스프링을 이해하려면 이전의 어떤 기술들을 이해하고 있어야 할까?
  • SOA
  • CBD
  • OOP
  • 절차적/구조적 프로그래밍
  • 기계어, 어셈블리어

1.2. 기계어에서 객체 지향 프로그래밍 언어로

기계어 - 0과 1의 행진 / 너무나 비인간적인 언어

기계어는 기계가 이해하는 유일한 언어로, 2진 숫자인 0과 1로만 표현된다. 기계어 코드는 CPU에 따라 다르다. 즉, 이기종 간에 호환이 불가능하다.

어셈블리어 - 0과 1의 행진을 벗어나 인간 지향으로 / 기계어 니모닉

니모닉(Mnemonic)과 기계어의 일대일 매칭 코드표 = 어셈블리

니모닉(Mnemonic)

어떤 것을 기억하는 데 쉽게 하도록 도움을 주는 것, 또는 쉽게 기억되는 성질. 니모닉은 재사용이 필요할 때 사람의 기억을 돕기 위해 명확하게 선택된 상징이나 상징의 조합을 사용하는 것을 말한다.

C언어 - 강력한 이식성 / One Source Multi Object Use Anywhere

C++ 언어 - 정말 인간적인 프로그래밍 방법론, 객체 지향

자바 - 진정한 객체 지향 언어

  • c++: 객체 지향 개념 도입
  • Java: 가상 머신 JVM
    • One Source Multi Object Use Anywhere -> Write Once Use Anywhere
  • C#: 가상 머신과 비슷한 공통 언어 런타임 CLR (Common Language Runtime)

1.3. 짧은 글, 긴 생각

UML을 대하는 자세

통합 모델링 언어(UML : Unified Modeling Language)는 소프트웨어 공학에서 사용되는 표준화된 범용 모델링 언어이다. 이 표준은 UML을 고안한 객체 관리 그룹에서 관리 하고 있다.

UML은 소프트웨어 집약 시스템의 시각적 모델을 만들기 위한 도안 표기법을 포함한다.

당신은 CBD, SOA가 어려운가?

CBD(Component Based Development; 컴포넌트 기반 개발)

CBD는 애플리케이션을 통짜로 개발하지 말고, 애플리케이션을 의미 있는 단위로 구분하고 그 단위를 하나 하나씩 부품으로 개발해 마치 레고 블록을 쌓아 올리듯 부품을 결합해 소프트웨어 제품을 완성하자는 방법론이자 기법이다. 제품이 아니다.

SOA(Service Oriented Architecture; 서비스 중심 구조, 서비스 지향 구조 )

SOA는 개발자 입장에서의 개발이 아니라 실제 현실의 업무를 기준으로 개발하자는 사상이다. 제품이 아니다.

CBD (Component Based Development), 컴포넌트 기반 개발

애플리케이션을 통짜로 개발하지 말고, 애플리케이션을 의미 있는 단위로 구분하고 그 단위를 하나하나씩 부품으로 개발해 마치 레고 블록을 쌓아 올리듯 부품을 결합해 소프트웨어 제품을 완성하자는 방법론이자 기법.

SOA (Service Oriented ARchitecture), 서비스 중심 구조 or 서비스 지향 구조

개발자 입장에서의 개발이 아니라 실제 현실의 업무를 기준으로 개발하자는 사상.

객체 지향의 4대 특성을 누군가에게 설명할 수 있는가?

객체 지향의 4대 특성, 객체 지향 설계 5원칙, 객체 지향의 베스트 프랙티스 모음인 디자인 패턴 등의 개념이 정립

스프링 프레임워크는 사상이면서 또 단일 제품이다

스프링 프레임 워크가 제품이라면 개념은 무엇일까?

바로 OOP 프레임워크

스프링 삼각형

IoC/DI, AOP, PSA. 스프링 삼각형은 POJO(Plain Old Java Object)에 세 가지 유형의 진동을 줌으로써 거대한 프레임워크를 완성해냈다.

스프링 프레임워크의 또 다른 아름다움은 엔터프라이즈 애플리케이션을 구현하는 데 필요한 거의 모든 서비스를 지원해준다는 것이다.

1.4. 책 출간의 변

우리가 알아야 할 것

  • OOP 개념
  • 자바 언어의 문법
  • 자바가 OOP 개념을 구현한 방식

[위로](# 목차)

002. 자바와 절차적/구조적 프로그래밍

2.1. 자바 프로그램의 개발과 구동

JDK - Java Development Kit / 자바 개발 도구

JRE - Java Runtime Environment / 자바 실행 환경

JVM - Java Virtual Machine / 자바 가상 기계

자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상의 컴퓨터인 JVM 상에서 구동된다.

다만 배포되는 JDK, JRE, JVM은 편의를 위해 JDK가 JRE를 포함하고 다시 JRE는 JVM을 포함하는 형태로 배포된다.

자바의 메모리 사용 방식

  • 프로그램이 메모리를 사용하는 방식: 코드 실행 영역 / 데이터 저장 영역
  • 객체 지향 프로그램의 메모리 사용 방식: 코드 실행 영역 / (스태틱 영역, 스택 영역, 힙 영역)

자바에 존재하는 절차적/구조적 프로그래밍의 유산

다시 보는 main() 메서드: 메서드 스택 프레임

main() 메서드는 프로그램이 실행되는 시작점이다. main() 메서드가 실행될 때 메모리, 특히 T 메모리에는 어떤 일이 일어날까?

데이터 저장 영역에 해당하는 static 영역, stack 영역, heap 영역을 T 메모리라고 부른다.

  • static 영역 >
  • 패키지나 클래스 정보가 올라간다.
  • 패키지나 클래스는 프로그램 시작과 동시에 모두 올라가는 것이 아니라, 실제로 호출될 때 올라간다.
  • 그러므로, static (전역) 변수는 읽기 전용이 아닌 경우에는 가능한 사용하지 말아야 한다.
  • class 영역 혹은 method 영역 이라고도 불린다.
  • static 영역에 자리잡게되면 JVM이 종료될 때까지 사라지지 않고, 고정된(static) 상태로 유지된다.
  • stack 영역 >
  • 여는 중괄호 '{'를 만날 때 마다 스택 프레임이 하나�씩 생기고, 닫는 중괄호 '}'를 만나게 되면 스택 프레임이 사라진다. 그러므로 메소드가 실행될 뿐 만 아니라, if문, 반복문, 예외처리를 위한 try문 등도 모두 스택프레임이 생긴다.
  • stack 내부에서 선언된 지역변수는 stack 영역에 올라간다.
  • 기본형 타입 변수의 값들은 stack영역에 저장되고, 참조형 타입 변수는 참조값만 저장된다. (이 참조값은 heap 영역에 존재하는 인스턴스(객체)를 가르키는 역할을 한다. 엄격한 표현은 아니지만 인스턴스(객체) 주소값 정도로 이해해도 된다.)
  • 외부 스택 프레임에서는 내부 스택 프레임의 변수에 접근 하는 것은 불가능하나 그 역은 가능하다. 쉽게 생각하면 메소드안에 for문 스택 프레임을 만든 경우, for문에서는 자신을 호출한 메소드의 변수는 사용가능하나, 메소드에서는 for문에서 선언한 변수를 사용 할 수 없다.
  • + 메소드를 호출하는 것은 별개의 스택프레임 이기 때문에 스택 프레임을 넘어서 접근할 수 없다.
  • 쓰레드도 stack 영역에 생기게 된다. 하나의 쓰레드는 내부적으로 별개의 T메모리 구조 static, stack, heap영역을 갖게 된다. 이런 이유로 하나의 쓰레드는 다른 쓰레드로 접근 할 수 없지만, static 영역과 heap 영역은 공유해서 사용 할 수있는 특징을 가지게 된다. (이런 특징이 멀티 프로레스 구조보다 멀티 쓰레드 구조가 메모리를 적게 사용 할 수 있는 이유이다.)
  • heap 영역 >
  • 생성된 객체(인스턴스)들이 올라간다.
  • 인스턴스 필드들은 heap 영역에 올라간다. (이러한 이유로 static한 메소드에서 인스턴스 멤버를 접근할 수가 없다. 어떤 인스턴스 인지도 알 수 없고, 존재하지도 않을 수도 있는 인스턴스를 사용하라는 것이기 떄문이다.)
  • 메소드들은 static이 아니더라도 굳이 heap에 생기지 않는다. 어차피 같은 로직의 메소드이기 때문에, 여러개 일 필요가 없다.
  • stack영역에서 참조값을 이용하여 참조형 변수가 heap 영역에 있는 인스턴스를 가르키어 제어 할 수 있게 된다.
  • 어떤 참조 변수도 힙영역에 있는 인스턴스를 참조하지 않게 된다면, GC(가비지 컬렉터)에 의해 메모리에서 사라지게 된다.
  • 상속을 이용한 인스턴스를 만들었다면 상위 클래스들의 인스턴스들도 같이 생성된다. (최상위인 Object까지)
	public class Start {
    public static void main(String[] args) {
      System.out.println("Hello OOP!!");
    }
  }
  1. JRE는 먼저 프로그램 안에 main() 메서드가 있는지 확인한다.

  2. main() 메서드의 존재가 확인되면 JRE는 가상의 기계인 JVM에 전원을 넣어 부팅한다.

  3. 부팅된 JVM은 목적 파일을 받아 그 목적 파일을 실행한다.

  4. JVM은 가장 먼저 java.lang 패키지(모든 자바 프로그램이 반드시 포함하게 되는 패키지)를 T 메모리의 스태틱 영역에 가져다 놓는다.

  5. 다음으로 JVM은 개발자가 작성한 모든 클래스와 임포트 패키지 역시 스태틱 영역에 가져다 놓는다.

    정리: main() 메서드가 실행되기 전 JVM에서 수행하는 전처리 작업들

    • java.lang 패키지를 T 메모리의 스태틱 영역에 배치한다.
    • import된 패키지를 T 메모리의 스태틱 영역에 배치한다.
    • 프로그램 상의 모든 클래스를 T 메모리의 스태틱 영역에 배치한다.
  6. main() 메서드의 스택 프레임이 스택 영역에 할당된다.

  7. 메서드 인자(들)의 변수 공간을 할당한다.

  8. 명령문 실행

  • T 메모리 구조
  • java.lang 패키지
  • import 패키지와 클래스들
  • 메서드 스택 프레임
  • JVM
  • JRE

2.2. 변수와 메모리: 변수! 너 어디 있니?

public class Start 2 {
  public static void main(String[] args) {
    int i;
    i = 10;
    
    double d = 20.0;
  }
}

main() 메서드 스택 프레임 안에 밑에서부터 차곡차곡 변수 공간을 마련한다.

2.3. 블록 구문과 메모리: 블록 스택 프레임

public class Start3 {
  public static void main(String[] args) {
    int i = 10;
    int k = 20;
    
    if(i == 10) {
      int m = k + 5;
      k = m;
    } else {
      int p = k + 10;
      k = p;
    }
    
    // k = m + p;
  }
}

if ~ else 블록, 여는 중괄호를 만나면 스택 프레임이 시작되는데 여기서 만들어지는 스택 프레임은 메서드의 스택 프레임이 아니라 if 문, 그것도 참인 블록의 스택 프레임이다. if 불록 중 참일 때의 블록을 종료하는 닫는 중괄호를 만나면 if 블록 스택 프레임은 스택 영역에서 사라진다. 이때 if 블록 스택 프레임 안에 상주하던 변수의 저장 공간도 함께 사라진다. main() 메서드 스택 프레임을 소멸시키는 블록 마침 기호인 닫는 중괄호에서는 T 메모리 소멸, JVM 가동 중지, JRE가 사용했던 시스템 자원을 운영체제에 반납한다.

2.4. 지역 변수와 메모리: 스택 프레임에 갇혔어요!

변수는 메모리에 있다? O

T 메모리 세 개의 영역 중 어디에? '세 군데 모두'

그런데 세 군데 각각에 있는 변수는 각기 다른 목적을 가진다. 그리고 각각의 이름도 지역 변수, 클래스 멤버 변수, 객체 멤버 변수로 다르다.

  • 지역 변수는 스택 영역에서 일생을 보낸다. 그것도 스택 프레임 안에서 일생을 보내게 된다. 따라서 스택 프레임이 사라지면 함께 사라진다.
  • 클래스 멤버 변수는 스태틱 영역에서 일색을 보낸다. 스태틱 영역에서 한번 자리 잡으면 JVM이 종료될 때까지 고정된(static) 상태로 그 자리를 지킨다.
  • 객체 멤버 변수는 힙에서 일생을 보낸다. 객체 멤버 변수들은 객체와ㅏ 함께 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 일생을 마치게 된다.
public static void main(String[] args) {
  if (true) {
    int i = 1;
    System.out.println(i); // 살행됨
  }
  
  System.out.println(i) // 참조 불가능 = 에러
  
}

"외부 스택 프레임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나 그 역은 가능하다"

2.5. 메서드 호출과 메모리: 메서드 스택 프레임 2

public class Start4 {
  public static void main(String[] args) {
    int k = 5;
    int m;
    
    m = square(k);
  }
  
  private static int square(int k) {
    int result;
    
    k = 25;
    
    result = k;
    
    return result;
  }
}

메서드 호출이 일어나면 무조건 호출되는 메서드의 스택 프레임이 T 메모리 스택 영역에 새로 생성된다. square() 메서드 스택 프레임에는 반환값을 저장할 변수 공간이 맨 아래, 그다음으로 인자를 저장할 변수 공간, 그리고 마지막으로 메서드의 지역 변수가 자리 잡는다.

주목해야 할 것은 main() 메서드가 가진 변수 k와 square() 메서드가 가진 변수 k가 이름만 같지 실제로는 서로 별도의 변수 공간이라는 것이다. 이것을 전문 용어로 Call By Value(값에 의한 호출)라 한다.

square() 메서드 내의 실행 명령문에서는 T 메모리 안에 존재하는 main() 메서드의 지역 변수를 참조할 수 있을 것 같지만 그건 자바 스펙을 마드신 분들이 금지시켜 뒀다.

  1. 그것이 이치에 맞기 때문이다. 메서드는 서로의 고유 공간인데, 서로 침범하면 무단 침입으로 자바 월드에 문제를 유발할 수 있기 때문이다.
  2. 포인터 문제 때문이다. square() 메서드에서 main() 메서드 내부의 지역변수 m에 접근한다고 하면 m의 위치를 명확히 ㅇ알아야 하는데, 그 위치를 명확히 알기 위해서는 바로 m 변수의 메모리 위치, 즉 포인터라고 읽고 메모리 주소 값이라 이해해야 하는 그 값을 알아야 한다.
  3. 예제의 코드는 square() 메서드를 main() 메서드 혼자서 호출하는 코드지만 실전에서 사용되는 메서드는 다양한 곳으로부터 호출된다. 이때 호출하는 메서드 내부의 지역 변수를 호출당하는 쪽에서 제어할 수 있게 코드를 만들려면 결국 포인터를 주고받아야 한다.

2.6. 전역 변수와 메모리: 전역 변수 쓰지 말라니까요!

public class Start5 {
  static int share;
  
  public static void main(String[] args) {
    share = 55;
    
    int k = fun(5, 7);
    
    System.out.println(share);
  }
  
  private static int fun(int m, int p) {
    share = m + p;
    
    return m - p;
  }
}

코드를 보면 share 변수에 static 키워드가 붙어있다. 그래서 share 변수는 T 메모리의 스태틱 영역에 변수 공간이 할당된다. 그것도 Start5 클래스 안에 정의됐으니 해당 클래스가 T 메모리 스태틱 영역에 배치될 때 그 안에 share 변수가 클래스의 멤버로 공간을 만들어 저장된다.

지역 변수와 전역 변수
  • 스택 프레임에 종속적인 지역 변수
  • 스택 프레임에 독립적인 전역 변수

전역 변수는 코드 어느 곳에서나 접근할 수 있다고 해서 전역 변수라고 하며, 여러 메서드들이 공유해서 사용한다고 해서 공유 변수라고도 한다.

전역 변수는 필할 수 있다면 즐기지 말고 피해야 할 존재다. 다만 읽기 전용으로 값을 공유해서 전역 상수로 쓰는 것은 적근 추천한다. 가장 대표적인 전역 상수 후보로는 원주율을 나타내는 PI값 등이 있다.

2.7. 멀티 스레드 / 멀티 프로세스의 이해

멀티 스레드(Multi Thread)의 T 메모리 모델은 스택 영역을 스레드 개수만큼 분할해서 쓰는 것이다.

멀티 프로세스는 다수의 데이터 저장 영역, 즉 다수의 T 메모리를 갖는 구조다.

멀티 프로세스는 각 프로세스마다 각자의 T 메모리가 있고 각자 고유의 공간이므로 서로 참조할 수 없다. 그에 반해 멀티 스레드는 하나의 T 메모리만 사용하는데 스택 영역만 분할해서 사용하는 구조다.

멀티 프로세스는 하나의 프로세스가 다른 프로세스의 T 메모리 영역을 절대 침범할 수 없는 메모리 안전한 구조이지만 메모리 사용량은 그만큼 크다.

멀티 스레드는 하나의 T 메모리 안에서 스택 영역만 분할한 것이기 때문에 하나의 스레드에서 다른 스레드의 스택 영역에는 접근할 수 없지만 스태틱 영역과 힙 영역은 공유해서 사용하는 구조다. 따라서 멀티 프로세스 대비 메모리를 적게 사용할 수 있는 구조다.

public class Start6 extends Thread {
    static int share;

    public static void main(String[] args) {
        Start6 t1 = new Start6();
        Start6 t2 = new Start6();

        t1.start();
        t2.start();
    }

    public void run() {
        for (int count = 0; count < 10; count++) {
            System.out.println(share++);

            try {sleep(10000);}
            catch (InterruptedException e) {}
        }
    }
}
1
0
2
2
3
4
5
6
7
7
8
9
10
10

2.8. STS(또는 이클립스)를 이용해 T 메모리 영역 엿보기

2.9. 정리 - 객체 지향은 절차적/구조적 프로그래밍의 어깨를 딛고

메서드를 만들 때는 순서도 또는 의사 코드를 작성하는 것이 좋다.

능력이 된다면 UML 액티비티 다이어그램을 그리는 것도 좋긴 하지만 메서드의 로직을 표현할 때는 순서도가 더 직관적인 것 같다.

NS 다이어그램, 나시 슈나이더만 다이어그램(Nassi-Shneiderman diagram)

  • 스태틱: 클래스의 놀이터
  • 스택: 메서드의 놀이터
  • 힙: 객체의 놀이터

[위로](# 목차)

003. 자바와 객체 지향

3.1. 객체 지향은 인간 지향이다

D&C : Divide and Conquer, 분할 정복

= 천 리 길도 한 걸음부터, 티끌 모아 태산

객체 지향은 인간의 인지 및 사고 방식까지 프로그래밍에 접목하는 인간(개발자) 지향을 실천하고 있는 것이다. 그래서 객체 지향은 직관적이다

3.2. 객체 지향의 4대 특성 - 캡! 상추다

  • 캡 - 캡슐화(Enapsulation): 정보 은닉(information hiding)
  • 상 - 상속(inheritance): 재사용
  • 추 - 추상화(Abstraction): 모델링
  • 다 - 다형성(Polymorphism): 사용 편의

3.3. 클래스 vs. 객체 = 붕어빵틀 vs. 붕어빵 ???

사람은 클래서, 김연아는 객체

펭귄은 클래스, 뽀로로는 객체

사람의 나이는 몇살인가? (X)

3.4. 추상화: 모델링

전산 분야에서 추상화는 영어로 Abstraction, 입체파 화가 피카소르 인해 널리 알려진 추상화는 영어로 Abstract Painting.

추상 : 여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용.

추상화란 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것이라고 정리할 수 있다.

  • 각체는 유일무이(unique)한 사물이다.
  • 클래스는 같은 특성을 지닌 여러 객체를 총칭하는 집합의 개념이다.

객체(object) = 클래스의 인스턴스

애플리케이션 경계, 컨텍스트(Context)

"내가 만들고자 하는 애플리케이션은 어디에서 사용될 것인가?"

추상화란 구체적인 것을 분해해서 관심 영역(애플리케이션 경계)에 있는 특성만 가지고 재조합하는 것 = 모델링

  • OOP의 추상화는 모델링이다.

  • 클래스 : 객체 = 펭귄 : 뽀로로

  • 클래스 설계에서 추상화가 사용된다.

  • 클래스 설계를 위해서는 애플리케이션 경계부터 정해야 한다.

  • 객체 지향에서 추상화의 결과는 클래스다.

  • 상속을 통한 추상화, 구체화

  • 인터페이스를 통한 추상화

  • 다형성을 통한 추상화

추상화 = 모델링 = 자바의 class 키워드

추상화와 T 메모리

  1. 추상화 및 모델링, UML 클래스 다이어그램
  2. 논리전 설계 -> 물리적 설계
  3. T 메모리 스냅샷 그리기

논리적 설계는 개발 환경(언어 등)에 영향을 받지 않는 설계이며, 물리적 설계는 개발 환경에 맞춰진 설계다.

클래스는 개념이면서 분류 체계일 뿐이므로 속성에 값을 가질 수 없다. 그러나 객체의 속성이지만 모든 객체가 같은 값을 가지고 있기에 클래스를 통해 질문해도 답을 알 수 있다. 같은 유형의 모든 객체가 같은 값을 가지고 있다면 static 키워드를 통해 클래스에 값을 저장한다.

클래스 멤버 = static 멤버 = 정적 멤버

객체 멤버 = 인스턴스 멤버

정적 속성은 해당 클래스의 모든 객체가 같은 값을 가질 때 사용하는 것이 기본이다. 물론 이외의 경우에도 쓸 수 는 있겠지만 그때는 정당한 논리를 가지고 써야 한다.

정적 메서드는 객체들의 존재 여부에 관계없이 쓸 수 있는 메서드다.

  • main() 메서드는 당연히 정적 메서드여야 한다. T 메모리가 초기화된 순간 객체는 하나도 존재하지 않기 때문에 객체 멤버 메서드를 바로 실행할 수 없다. 따라서 main() 메서드는 정적 메서드여야 한다.
  • main() 메서드의 논리를 함수로 분할해서 사용하는 경우와 정적 변수에 대한 접근자 메서드(getter)와 설정자 메서드(setter)로 사용하는 용도

3.5. 상속: 재사용 + 확장

객체 지향에서 상속이란 일반인들이 생각하는 상속이 아닌 확장, 세분화, 슈퍼 클래스 - 서브 클래스(상위 클래스 - 하위 클래스) 개념으로 이해해야 한다.

자바 언어에서 inheritance(상속) 라는 키워드는 존재하지 않는다. 대신 extends(확장) 가 존재한다.

is a 관계는 객치(클래스의 인스턴스)와 클래스의 관계로 오해될 소지가 많다. is a 관계라고 표현할 때 오해를 일으킬 수 있는 예문.

  • 객체 is a 클래스
  • 김연아 is a 사람 -> 김연아는 한 명의 사람이다.
  • 뽀로로 is a 펭귄 -> 뽀로로는 한 마리의 펭귄이다.
  • 뽀로로 is a 조류 -> 뽀로로는 한 마리의 조류다.
  • 뽀로로 is a 동물 -> 뽀로로는 한 마리의 동물이다.

상속 관계의 더 명확한 영어 표현, is a kind of

상속과 인터페이스

  • 상속 관계: 하위 클래스 is a kind of 상위 클래스

  • 해석: 하위 클래스는 상위 클래스의 한 분류다.

  • 예제: 고래는 동물의 한 분류다.

  • 인터페이스: 구현 클래스는 is able to 인터페이스

  • 해석: 구현 클래스는 인터페이스할 수 있다.

  • 예제: 고래는 헤엄칠 수 있다.

  • Serializable 인터페이스: 직렬화할 수 있는

  • Cloneable 인터페이스: 복제할 수 있는

  • Comparable 인터페이스: 비교할 수 있는

  • Runnable 인터페이스: 실행할 수 있는

  • 상위 클래스는 하위 클래스에게 물려줄 특성이 많을수록 좋을까? 적을수록 좋을까?

    • 물려줄 특성이 많다면 그만큼 중복된 코드를 줄일 수 있다.
    • 그치만 어떻게 생각하면 서로 다른 특성을 가진 객체를 만들 수 있는 기회를 뺏앗는 것이 아닐까?
  • 인터페이스는 구현을 강제할 메서드가 많을수록 좋을까? 적을수록 좋을까?

    • 메서드가 많을수록 코드 작성이 많아질 것 같은데...

객제 지향 설계 5원칙 중에서 상위 클래스가 풍성할수록 좋은 이유는 LSH(리스코프 치환 원칙)에 따른 이유

인터페이스에 메서드가 적을수록 좋은 이유는 ISP(인터페이스 분할 원칙)에 따른 이유.

=> 5장에서 이어감

상속과 UML 표기법 (누적해서 정리)

  1. 두 클래스 간의 상송을 표현하기 위해 하위 클래스에서 상위 클래스 쪽으로 화살표를 그린다. 화살표는 속이 비어있고 닫힌 삼각형 머리에 실선 꼬리를 가진 형태로 그린다.
  2. 클래스가 인터페이스를 구현한 경우에는 인터페이스를 구현하는 클래스에서 인터페이스 쪽으로 화살표를 그린다. 이때 화살표는 꼬리가 점선인 점만 제외하고 상송 화살표와 모든 것이 같다. 인터페이스 구현에 대한 약식 표기로 막대 사탕을 사용하기도 한다.
  3. ...

상속과 T 메모리

public class Driver {
    public static void main(String[] args) {
        Penguin pororo = new Penguin();
        Penguin pingu = new Penguin();
    }
}

기억 상실처럼 동물인 것은 인식하지만 펭귄이라는 사실은 모른다.

3.6. 다형성: 사용편의성

객체 지향에서 다형성이라고 하면 오버라이딩(overriding)과 오버로딩(overloading)이라고 할 수 있다.

  • 같은 메서드 이름, 같은 인자 목록으로 상위 클래스의 메서드를 재정의
  • 같은 메서드 이름, 다른 인자 목록으로 다수의 메서드를 중복 정의

다형성이 지원되지 않는 언어

오버로딩은 함수명 하나를 가지고 인자 목록만 달리하면 된다. 제네릭을 이용하면 하나의 함수만 구현해도 다수의 함수를 구현한 효과를 낼 수 있다.

오버라이딩의 경우에도 하위 클래스가 재정의한 메서드를 알아서 호출해 줌으로써 형변환이나 instanceof 연산자를 써서 하위 클래스가 무엇인지 신경 쓰지 않아도 된다. 상위 클래스 타입의 객체 참조 변수에서 하위 클래스가 오버라이딩한 메서드를 자동으로 호출해 줌으로써 깔끔한 코드를 유지할 수 있게 된다.

오버라이딩을 통한 메서드 재정의, 오버로딩을 통한 메서드 중복 정의를 통해 다형성을 제공하고 이 다형성이 개발자가 프로그램을 작성할 때 사용편의성을 준다.

3.7. 캡슐화: 정보 은닉

자바에서 정보 은닉이라고 하면 접근 제어자인 private, [default], protected, public이 생각날 것이다. 접근 제어자가 객체 멤버(인스턴스 멤버)와 쓰일 때와 정적 멤버(클래스 멤버)와 함께 쓰일 때를 비교해보자.

객체 멤버의 접근 제어자

ClassA의 객체 멤버인 pri, def, pro, pub 속성이 보인다. UML 표기법에서 - 표시는 private 접근 제어자를, ~ 표시는 [default] 접근 제어자를, # 표시는 protected 접근 제어자를, + 표시는 public 접근 제어자를 나타낸다. 속성이나 메서드 아래에 _(밑줄)을 사용한 경우는 정적 멤버를 나타낸다.

package encapsulation01.packageOne;

public class ClassA {
  private int pril;
  int def;
  protected int pro;
  public int pub;
  
  void runSomething() {
    
  }

  static void runStaticThing() {
    
  }
}

protected 가 자신과 상속 관계에 있는 서브 클래스만 접근 가능한 걸로 착각하는 경우가 많다. 같은 패키지라면 한 집에 산다고 생각하기에 접근 가능하다는 사실도 꼭 기억해야 한다. 습관적으로 private 아니면 public만 사용하거나, 그냥 아무 표시도 하지 않는 [default]만 사용해 왔다면 반성이 필요한 대목이다.

  • 상속을 받지 않았다면 객체 멤버는 객체를 생성한 후 객체 참조 변수를 이용해 접근해야 한다.
  • 정적 멤버는 클래스명.정적멤버 형식으로 접근하는 것을 권장한다.

참조 변수의 복사

  • Call By Value

  • Call By Reference or Call By Address��

  • 기본 자료형 변수는 값을 값 자체로 판단한다.

  • 참조 자료형 변수는 값을 주소, 즉 포인터로 판단한다.

  • 기본 자료형 변수를 복사할 때, 참조 자료형 변수를 복사할 때 일어나는 일은 같다. 즉 가지고 있는 값을 그대로 복사해서 넘겨 준다.

정리 - 자바 키워드와 OOP 4대 특성

표 참조. - 144page

[위로](# 목차)

004. 자바가 확장한 객체 지향

4.1. abstract 키워드 - 추상 메서드와 추상 클래스

추상 메서드(Abstract Method) - 선언부는 있는데 구현부가 없는 메서드

추상 메서드를 하나라도 갖고 있는 클래스는 반드시 추상 클래스로 선언해야 한다. 물론 추상 메서드 없이도 추상 클래스를 선언할 수는 있다.

추상 클래스를 상송한 하위 클래스는 추상 메서드를 오버라이딩하지 않으면 컴파일 시점에 에러가 발생한다.

엥? 그렇다면 인터페이스랑 추상 클래스의 차이는 뭐지??

신입 개발자 교육때 내내 다룬건데 다시 개념이 헷갈린다...

  • 추상 클래스는 인스턴스, 즉 객체를 만들 수 없다. 즉, new를 사용할 수 없다.
  • 추상 메서드는 하위 클래스에게 메서드의 구현을 강제한다. 오버라이딩 강제.
  • 추상 메서드를 포함하는 클래스는 반드시 추상 클래스여야 한다.

4.2. 생성자

클래스명() - 반환값이 없고 클래스명과 같은 이름을 가진 메서드를 객체를 생성하는 메서드라고 해서 객체 생성자 메서드라 한다. 그리고 줄여서 생성자라는 이름으로 더 많이 부른다.

  • 개발자가 아무런 생성자도 만들지 않으면 자바는 인자가 없는 기본 생성자를 자동으로 만들어준다,
  • 인자기 있는 생성자를 하나라도 만든다면 자바는 기본 생성자를 만들어 주지 않는다.

4.3. 클래스 생성 시의 실행 블록, static 블록

객체 생성자가 있다면 클래스 생성자도 있을 거라고 기대해 볼 만 하다. 자바는 그 기대의 절반만 부응해 준다. 클래스 생성자는 존재하지 않는다. 그러나 클래스가 스태틱 영역에 배치될 때 실행되는 코드 블록이 있다. 바로 static 블록이다.

static 블록에서 사용할 수 있는 속성과 메서드는 당연히 static 멤버 뿐이다.

동물 인스턴스를 여러 개 만들어도 동물 클래스의 statifc 블록은 단 한 번만 실행되는 것을 알 수 있다.

클래스 정보는 해당 클래스가 코드에서 맨 처음 사용될 때 T 메모리의 스태틱 영역에 로딩되며, 이때 단 한번 해당 클래스의 static 블록이 실행된다.

여기서 클래스가 제일 처음 사용될 때는 다음 세 가지 경우 중 하나다.

  • 클래스의 정적 속성을 사용할 때
  • 클래스의 정적 메서드를 사용할 때
  • 클래스의 인스턴스를 최초로 만들 때

왜 프로그램이 실행될 때 바로 클래스들의 정보를 T 메모리의 static 영역에 로딩하지 않고 해당 클래스가 처음 사용될 때 로딩할까?

=> 스태틱 영역도 메모리이기 때문이다. 메모리는 최대한 늦게 사용을 시작하고 최대한 빨리 반환하는 것이 정석이다.

물론 자바는 스태틱 영역에 한번 올라가면 프로그램이 종료되기 전까지는 해당 메모리를 반환할 수 없지만 그럼에도 최대한 늦게 로딩함으로써 메모리 사용을 최대한 늦추기 위해서다.

왜 자바에서는 스태틱 영역에 한번 올라가면 메모리를 해제할 수 없을까??

전역변수가 프로그램이 종료될 때 까지 어디서든 사용이 가능한 이유.

정의 혹은 사용 목적이 그러하다 할지라도 코드를 통해 의도대로 해제할 수 있어야 하지 않을까?

=> 그런 의도면 애초에 스택메모리 사용하면 되는건가 싶기도...?

4.4. final 키워드

final은 마지막, 최종이라는 의미를 가진 단어다. final 키워드가 나타날 수 있는 곳은 딱 세군데다. 사실 객체 지향 언어의 구성 요소는 딱 세가지 뿐이다. 바로 클래스, 변수, 메서드다.

4.4.1. final가 클래스

// 상속을 허락하지 않겠다는 의미
public final class 고양이 {}
// The type 길고양이 cannot subclass the final class 고양이
// 길고양이 타입은 최종 클래스인 고양이의 하위 분류가 될 수 없다.
public class 길고양이 extends 고양이{}

4.4.2. final과 변수

public class 고양이 {
  final static int 정적상수1 = 1;
  final static int 정적상수2;
  
  final int 객체상수1 = 1;
  final int 객체상수2;
  
  static {
    정적상수2 =. ;
    
    // 상수는 한 번 초기화되면 값을 변경할 수 없다.
    // 정적상수2 = 4;
  }
  
  고양이() {
    객체상수2 = 2;
    
    // 상수는 한 번 초기화되면 값을 변경할 수 없다.
    // 객체상수2 = 4;
    
    final int 지역상수1 = 1;
    final int 지역상수2;
    
    지역상수2 = 2;
  }
}

다른 언어에서는 읽기 전용인 상수에 대해 final 키워드 대신 const 키워드를 사용하기도 하는데 자바에서는 이런 혼동을 피하기 위해 const 키워드를 등록해두고 쓰지 못하게(not used) 하고 있다.

4.4.3. final과 메서드

메서드가 final이라면 최종이니 재정의, 즉 오버라이딩을 금지하게 된다.

4.5. instanceof 연산자

인스턴스는 클래스를 통해 만들어진 객체라고 했다. instanceof 연산자는 만들어진 객체가 특정 클래스의 인스턴스인지 물어보는 연산자다. instanceof 연산자는 결과로 true 또는 false를 반납한다. 사용법은 아래와 같다.

객체_참조_변수 instanceof 클래스명

instanceof 연산자가 강력하기는 하지만 객체 지향 설계 5원칙 가운데 LSP(리스코프 치환 원칙)를 어기는 코드에서 주로 나타나는 연산자이기에 코드에 instanceof 연산자가 보인다면 냄새 나는 코드가 아닌지, 즉 리펙터링의 대상이 아닌지 점검해 봐야 한다.

instanceof 연산자는 클래스들의 상속 관계뿐만 아니라 인터페이스의 구현 관계에서도 동일하게 적용된다.

4.6. package 키워드

package 키워드는 네임스페이스(이름공간)를 만들어주는 역할을 한다. 거창하기는 하지만 특별히 하는 일은 없다.

4.7. interface 키워드와 implements 키워드

인터페이스는 public 추상 메서드와 public 정적 상수만 가질 수 있다.

때문에 따로 메서드에 public과 abstract, 속성에 public과 static, final을 붙이지 않아도 자바가 알아서 붙여준다.

4.8. this 키워드

this는 객체가 자기 자신을 지칭할 때 쓰는 키워드다.

  • 지역 변수와 속성(객체 변수, 정적 변수)의 이름이 같은 경우 지역 변수가 우선한다
  • 객체 변수와 이름이 같은 지역 변수가 있는 경우 객체 변수를 사용하려면 this를 접두사로 사용한다.
  • 정적 변수와 이름이 같은 지역 변수가 있는 경우 정적 변수를 사용하려면 클래스명을 접두사로 사용한다.

4.9. super 키워드

단일 상속만을 지원하는 자바에서 super는 바로 위 상위 클래스의 인스턴스를 지칭하는 키워드다.

4.10. 예비 고수를 위한 한마디

똑같은 객체 멤버 메서드를 힙 영역에 여러 개를 만든다는 것은 심각한 메모리 낭비라고 할 수 있다. 그래서 JVM은 지능적으로 객체 멤버 메서드를 스태틱 영역에 단 하나만 보유한다. 그리고 눈에 보이지는 않지만 메서드를 호출할 때 객체 자신을 나타내는 this 객체 참조 변수를 넘긴다.

4.11. 정리 - 자바 키워드와 OOP 확장

[위로](# 목차)

005. 객체 지향 설계 5원책 - SOLID

응집도는 높이고(High Cohesion), 결합도는 낮추라(Loose Coupling)는 고전 원칙을 객체 지향의 관점에서 재정립한 것.

5.1. SRP - 단일 책임 원칙

"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다" - 로버트 C. 마틴

역할(책임)을 분리하라는 것이 단일 책임 원칙이다. 단일 책임 원칙은 속성, 메서드, 패키지, 모듈, 컴포넌트, 프레임워크 등에도 적용할 수 있는 개념이다.

단일 책임 원칙은 잘된 경우보다 잘못된 경우를 살펴보는 것이 이해하는 데 좋다.

5.1.1. 속성이 단일 책임 원칙을 지키지 못하는 경우

class 사람 {
  String 군번;
}

사람 로미오 = new 사람();
사람 줄리엣 = new 사람();

줄리엣.군번 = "1573042009"; // 이건?

사람형 참조 변수 줄리엣이 가진 군번 속성에 값을 할당하거나 읽어 오는 코드를 제어할 방법이 없다.

사람 클래스를 남자 클래스와 여자 클래스로 분할하고 남자 클래스에만 군번 속성을 갖게 하는 것이 좋다.

5.1.2. 하나의 속성이 여러 의미를 갖는 경우

데이터베이스 테이블을 설계할 때는 정규화라고 하는 과정을 거치게 되는데, 정규화 과정을 조금 더 확장해서 생각해 보면 테이블과 필드에 대한 단일 책임 원칙의 적용이라고 할 수 있다.

class 강아지 {
  final static Boolean 수컷 = true;
  final static Boolean 암컷 = false;
  Boolean 성별;
  
  void 소변보다() {
    if (this.성별 == 수컷) {
      // 한쪽 다리를 들고 소변을 본다.
    } else {
      // 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
    }
  }
}

강아지 클래스의 소변보다() 메서드가 수컷 강아지의 행위와 암컷 강아지의 행위를 모두 구현하려고 하기에 단일 책임(행위) 원칙을 위배하고 있는 것이다. 메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적인 냄새가 바로 분기 처리를 위한 if문이다. 이런 경우 단일 책임 원칙을 적용해 코드를 리팩터링하면 아래와 같이 만들 수 있다.

abstract class 강아지 {
  abstract void 소변보다()
}

class 수컷강아지 extends 강아지 {
  void 소변보다() {
    // 한쪽 다리를 들고 소변을 본다.
  }
}

class 암컷강아지 extends 강아지 {
  void 소변보다() {
    // 뒷다리 두 개를 굽혀 앉은 자세로 소변을 본다.
  }
}

5.2. OCP - 개방 폐쇄 원칙

"소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다." - 로버트 C. 마틴

=> "자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다"

개방 폐쇄 원칙을 따르지 않는다고 해서 객체 지향 프로그램을 구현하는 것이 불가능한 것은 아니지만 개방 폐쇄 원칙을 무시하고 프로그램을 작성하면 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없다.

5.3. LSP - 리스코프 치환 원칙

"서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다." - 로버트 C. 마틴

객체 지향의 상속 조건

  • 하위 클래스 is a kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류다.
  • 구현 클래스 is able to 인터페이스 - 구현 분류는 인터페이스할 수 있어야 한다.

위 두 개의 문장대로 구현된 프로그램이라면 이미 리스코프 치환 원칙을 잘 지키고 있다고 할 수 있다.

"하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없어야 한다."

리스코프 치환 원칙

  • 하위형에서 선행 조건은 강화될 수 없다.
  • 하위형에서 후행 조건은 약화될 수 없다.
  • 하위형에서 상위형의 불변 조건은 반드시 유지 돼야 한다.

5.4. ISP - 인터페이스 분리 원칙

"클라이언트는 자신이 사용하지 않는 메서드의 의존 관계를 맺으면 안 된다." - 로버트 C. 마틴

단일 책임 원칙(SRP)과 인터페이스 분할 원칙(ISP)은 같은 문제에 대한 두 가지 다른 해결책이라고 볼 수 있다. 프로젝트 요구사항과 설계자의 취향에 따라 단일 책임 원칙이나 인터페이스 분할 원칙 중 하나를 선택해서 설계할 수 있다. 하지만 특별한 경우가 아니라면 단일 책임 원칙을 적용하는 것이 더 좋은 해결책 이라고 할 수 있다.

=> 인터페이스 최소주의 원칙: 인터페이스를 통해 메서드를 외부에 제공할 때는 최소한의 메서드만 제공하라는 것.

5.5. DIP - 의존 역전 원칙

"고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다."

"추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다."

"자주 변경되는 구체(Concrete) 클래스에 의존하지 마라" - 로버트 C. 마틴

자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 번 하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다.

"자신보다 변하기 쉬운 것에 의존하지 마라."

상위 클래스일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 것이 바로 의존 역전 원칙이다.

5.6. 정리 - 객체 지향 세계와 SOLID

SOLID를 이야기할 때 빼놓을 수 없는 것이 SoC다. SoC는 관심사의 분리(Separation Of Concerns)의 머리글자다. 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모으고, 관심이 다른 것은 가능한 한 따로 떨어져 서로 영향을 주지 않도록 분리하라는 것이다. 하나의 속성, 하나의 메서드, 하나의 클래스, 하나의 모듈, 또는 하나의 패키지에는 하나의 관심사만 들어 있어야 한다는 것이 SoC다.

  • SRP(단일 책임 원칙): 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
  • OCP(개방 폐쇄 원칙): 자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
  • LSP(리스코프 치환 원칙): 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
  • ISP(인터페이스 분리 원칙): 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.
  • DIP(의존 역전 원칙): 자신보다 변하기 쉬운 것에 의존하지 마라.

[위로](# 목차)

006. 스프링이 사랑한 디자인 패턴

디자인 패턴은 실제 개발 현장에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양한 해결책 중에서 많은 사람들이 인정한 베스트 프랙티스를 정리한 것이다. 디자인 패턴은 당연히 객체 지향 특성과 설계 원칙을 기반으로 구현돼 있다.

"자바 인터프라이즈 개발을 편하게 해주는 오픈소그 경량급 애플리케이션 프레임워크"

"OOP 프레임워크"

디자인 패턴은 객체 지향의 특성 중 상속(extends), 인터페이스(interface/implements), 합성(객체를 속성으로 사용)을 이용한다.

6.1. 어댑터 패턴(Adapter Pattern)

어댑터(변환기)의 역할은 서로 다른 두 인터페이스 사이에 통신이 가능하게 하는 것이다.

다양한 데이터베이스 시스템을 공통의 인터페이스인 ODBC 또는 JDBC를 이용해 조작할 수 있다.

어댑터 패턴은 개방 폐쇄 원칙을 활용한 설계 패턴이라고 할 수 있다.

"호출당하는 쪽의 메서드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴"

6.2. 프록시 패턴(Proxy Pattern)

프록시는 대리자, 대변인이라는 뜻을 가진 단어다.

프록시 패턴의 경우 실제 서비스 객체가 가진 메서드와 같은 이름의 메서드를 사용하는데, 이를 위해 인터페이스를 사용한다. 인터페이스를 사용하면 서비스 객체가 들어갈 ㅈㅏ리에 대리자 객체를 대신 투입해 클라이언트 쪽에서는 실제 서비스 객체를 통해 메서드를 호출하고 반환값을 받는지, 대리자 객체를 통해 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리할 수도 있다.

  • 대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 ㅅㅏ용한다.
  • 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성).
  • 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.
  • 대리자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다.

"제어 흐름을 조절하기 위한 목적으로 중간에 대리자를 두는 패턴"

6.3. 데코레이터 패턴(Decorator Pattern)

데코레이터는 도장/도배업자를 의미한다. (장식자라고 표현)

데코레이터 패턴은 프록시 패턴과 구현 방법이 같다. 다만 프록시 패턴은 클라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고 그대로 전달하는 반면 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힌다.

  • 장식자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
  • 장식자는 실제 서비스에 대한 참조 변수를 갖는다(합성).
  • 장식자는 실제 서비스와 같은 이름을 가진 메서드를 호출하고, 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
  • 장식자는 실제 서비스의 메서드 호출 전후에 별도의 로직을 수행할 수도 있다.

"메서드 호출의 반환값에 변화를 주기 위해 중간에 장식자를 두는 패턴"

데코레이터 패턴이 프록시 패턴과 동일한 구조를 갖기에 데코레이터 패턴도 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 설계 패턴임을 알 수 있다.

6.4. 싱글턴 패턴(Singleton Pattern)

싱글턴 패턴이란 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 경우 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 또 프로그램이 예상치 못한 결과를 낳을 수 있다. 싱글턴 패턴은 오직 인스턴스를 하나만 만들고 그것을 계속해서 재사용한다.

싱글턴 패턴을 적용할 경우 의미상 두 개의 객체가 존재할 수 없다. 이를 구현하려면 객체 생성을 위한 new에 제약을 걸어야 하고, 만들어진 단일 객체를 반환할 수 있는 메서드가 필요하다. 따라서 필요한 요소를 생각해 보면 다음 세 가지가 반드시 필요하다.

  • new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
  • 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
  • 유일단 단일 객체를 참조할 정적 참조 변수가 필요하다.

싱글톤 패턴의 특징

  • private 생성자를 갖는다.
  • 단일 객체 참조 변수를 정적 속성으로 갖는다.
  • 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메서드를 갖는다.
  • 단일 객체는 쓰기 가능한 속성을 갖지 않는 것이 정석이다.

"클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴"

6.5. 템플릿 메서드 패턴(Template Method Pattern)

상위 클래스에 공통 로직을 수행하는 템플릿 메서드와 하위 클래스에 오버라이딩을 강제하는 추상 메서드 또는 선택적으로 오버라이딩할 수 있는 훅(Hook) 메서드를 두는 패턴을 템플릿 메서드 패턴이라고 한다.

"상위 클래스의 견본 메서드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴"

템플릿 메서드 패턴이 의존 역전 원칙(DIP)을 활용하고 있음을 알 수 있다.

6.6. 팩터리 메서드 패턴(Factory Method Pattern)

팩터리 메서드는 객체를 생성 반환하는 메서드를 말한다. 여기에 패턴이 붙으면 하위 클래스에서 팩터리 메서드를 오버라이딩해서 객체를 반환하게 하는 것을 의미한다.

"오버라이드된 메서드가 객체를 반환하는 패턴"

팩터리 메서드 패턴은 의존 역전 원칙(DIP)을 활용하고 있다.

6.7. 전략 패턴(Strategy Pattern)

디자인 패턴의 꽃

전략 패턴을 구성하는 세 요소

  • 전략 메서드를 가진 전략 객체
  • 전략 객체를 사용하는 컨텍스트(전략 객체의 사용자/소비자)
  • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트(제3자, 전략 객체의 공급자)

클라이언트는 다양한 전략 중 하나를 선택해 생성한 후 컨텍스트에 주입한다.

단일 상속만이 가능한 자바 언어에서는 상속이라는 제한이 있는 템플릿 메서드 패턴보다는 전략 패턴이 더 많이 활용된다.

"클라이언트가 전략을 생성해 전략을 실행할 컨텍스트에 주입하는 패턴"

전략 패턴은 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 것이다.

6.8. 템플릿 콜백 패턴(Template Callback Pattern - 견본/회신 패턴)

템플릿 콜백 패턴은 전략 패턴의 변형으로, 스프링의 3대 프로그래밍 모델 중 하나인 DI(의존성 주입)에서 사용하는 특별한 형태의 전략 패턴이다. 템플릿 콜백 패턴은 전략 패턴과 모든 것이 동일한데 전략을 익명 내부 클래스로 정의해서 사용한다는 특징이 있다.

스프링은 리팩터링된 템플릿 콜백 패턴을 DI에 적극 활용하고 있다.

"전략을 익명 내부 클래스로 구현한 전략 패턴"

템플릿 콜백 패턴은 전략 패턴의 일종이므로 당연히 개방 폐쇄 원칙(OCP)과 의존 역전 원칙(DIP)이 적용된 설계 패턴이다.

6.9. 스프링이 사랑한 다른 패턴들

지금까지 살펴본 8가지 패턴 말고도 스프링은 다양한 디자인 패턴을 활용하고 있다. 특히 스프링 MVC의 경우에는 프론트 컨트롤러 패턴(Front Controller Pattern; 최전선 제어자 패턴)과 MVC( Model - View - Controller)을 활용하고 있다.

[위로](# 목차)

007. 스프링 삼각형과 설정 정보

스프링을 이해하는 데는 POJO (Plain Old Java Object)를 기반으로 스프링 삼각형이라는 애칭을 가진 IoC/DI, AOP, PSA라고 하는 스프링의 3대 프로그래밍 모델에 대한 이해가 필수다.

7.1. IoC/DI - 제어의 역전/의존성 주입

자동차를 생성함과 동시에 타이어를 장착한다 -> 타이어 브랜드 유동성이 떨어짐

자동차 생성자에 타이어를 받는다 -> 자동차 생성 시 원하는 타이어를 장착할 수 있다

생성자를 통해 의존성 주입

스프링을 통한 의존성 주입 - @Autowired vs. @Resource

@Autowired @Resource
출처 스프링 프레임워크 표준 자바
소속 패키지 org.springframework.beans.factory.annotation.Autowired javax.annotation.Resource
빈 검색 방식 byType 먼저, 못 찾으면 byNAme byName 먼저, 못 찾으면 byType
특이사항 @Qualifier("") 협업 name 어트리뷰트
byName 강제하기 @Autowired @Qualifier("tire1") @Resource(name="tire1")

7.2. AOP - Aspect? 관점? 핵심 관심사? 횡단 관심사?

AOP(Aspect-Oriented Programming) - 관점 지향 프로그래밍?

스프링 DI가 의존성에 대한 주입이라면 스프링 AOP는 로직 주입

  • @Aspect - 이 클래스를 AOP에서 사용하겠다.

  • @Before - 대상 메서드 실행 전에 이 메서드를 실행하겠다.

  • 스프링 AOP는 인터페이스(interface) 기반이다.

  • 스프링 AOP는 프록시(proxy) 기반이다.

  • 스프링 AOP는 런타임(runtime) 기반이다.

Pointcut - 자르는 지점? Aspect 적용 위치 지정자!

JoinPoint - 연결점? 연결 가능한 지점!

Advice - 조언? 언제, 무엇을!

Aspect - 관점? 측면? Advisor의 집합체!

Advisor - 조언자? 어디서, 언제, 무엇을!

7.3. PSA - 일관성 있는 서비스 추상화

PSA (Portable Service Abstraction)

어댑터 패턴을 적용해 같은 일을 하는 다수의 기술을 공통의 인터페이스로 제어할 수 있게 한 것.

[위로](# 목차)

A. 스프링 MVC를 이용한 게시판 구축

A.1. URL과 @RequestMapping 연결하기

A.2. 인메모리 DB HSQL 사용하기

A.3. VO와 MyBatis를 이용한 DAO 구현

A.4. 서비스(Service) 구현

A.5. 목록 구현

A.6. 읽기 구현

A.7. 새 글 구현

A.8. 수정 구현

A.9. 삭제 구현

A.10. 리펙터링

[위로](# 목차)

B. 자바 8 람다와 인터페이스 스펙 변화

기업 환경 변화와 프로그래머들의 요구를 반영하기 위해 자바 8은 언어적으로 많은 변화를 맞이했다. 특히 함수형 프로그래밍 지원을 위한 람다(Lambda)의 도입이 두드러진다.

B.1. 람다가 도입된 이유

빅데이터 분석은 당연히 ICT 기술을 통해 이뤄질 수 밖에 없다.

ICT, Information and Communications Technology; 정보 통신 기술:

정보기술의 확장형 동의어로 자주 사용되지만, 통합 커뮤니케이션의 역할과 원거리 통신, 컴퓨터, 더 나아가 정보에 접근하여 그것을 저장하고 전송하고 조작할 수 있게 하는 필수적인 전사적 소프트웨어, 미들웨어, 스토리지, 오디오 비주얼 시스템을 강조하는 용어이다.

따라서 프로그래머들에게 빅데이터를 프로그램적으로 다룰 수 있는 방법이 필요해졌다. 그 방법의 중심에는 다시 멀티 코어를 활용한 분산 처리, 즉 병렬화 기술이 필요하다.

기존의 CPU는 내부에 코어를 하나만 가지고 있었다. 이러한 CPU를 멀티태스킹 환경에서 사용하기 위해 시분할과 같은 기술이 사용되기는 했지만 일반 프로그래머들에게는 별로 큰 영향을 주지 않았다. 그런데 점차 규모가 커져만 가는 데이터를 처리하기 위한 방법이 필요했고, 이때 사용하던 기술은 웹가든(Web Garden)이나 웹팜(Web Farm)을 통해 CPU를 다수 사용하거나, 다수의 하드웨어를 사용하는 형태였다.

B.2. 람다란 무엇인가?

public class B002 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            // 자동완성하면 여기에 오버라이드 뜨던데 차이는?
            // @Override
            public void run() {
                System.out.println("Hello Lambda 2!!!");
            }
        };

        r.run();
    }
}

new Runnable()이 사라질 수 있었던 이유를 살펴보면 Runnalbe 타입으로 참조 변수 r을 만들고 있으니 new Runnalbe()은 컴파일러가 알아낼 수 있다.

public void run() 메서드가 단순하게 ()로 변경될 수 있는 이유 역시 간단하다. Runnable 인터페이스가 가진 추상 메서드가 run() 메서드 단 하나이기 때문이다.

마지막으로 추가된 부분은 화살표 기호인 ->다. 이는 람다의 구조가 (인자 목록) -> {로직} 이기 때문이다. 단 한 줄로 표기되는 경우 블록 기호 {}마저 생략할 수 있다.

B.3. 함수형 인터페이스

추상 메서드를 하나만 갖는 인터페이스를 자바 8부터는 함수형 인터페이스라고 한다. 이런 함수형 인터페이스만을 람다식으로 변경할 수 있다.

public class B005 {
    public static void main(String[] args) {
        MyFunctionalInterface mfi = (int a) -> { return a + a; };

        int b = mfi.runSomething(5);

        System.out.println(b);
    }
}

@FunctionalInterface
interface MyFunctionalInterface {
    public abstract int runSomething(int count);
}

인터페이스인 MyFunctionalInterface 위에 @FunctionalInterface 어노테이션을 붙이는 것은 옵션이다. 이 어노테이션이 붙은 경우 컴파일러는 인터페이스가 함수형 인터페이스의 조건에 맞는지 검사한다. 즉, 단 하나의 추상 메서드만을 갖고 있는지 확인한다.

B.4. 메서드 호출 인자로 람다 사용

람다식을 변수에 저장하는 것이 가능하다면 당연히 메서드의 인자로도 사용할 수 있다. 코드 블록을 메서드의 인자로 전달할 수 있는 것이다.

람다식을 단 한번만 사용한다면 굳이 변수에 할당할 필요도 없다.

B.5. 메서드 반환값으로 람다 사용

public class B009 {
    public static void main(String[] args) {
        MyFunctionalInterface mfi = todo();

        int result = mfi.runSomething(3);

        System.out.println(result);
    }

    public static MyFunctionalInterface todo() {
        return num -> num * num;
    }
}

B.6. 자바 8 API에서 제공하는 함수형 인터페이스

함수형 인터페이스 추상 메서드 용도
Runnable void run() 실행 할 수 있는 인터페이스
Supplier T get() 제공 할 수 있는 인터페이스
Consumer void accept(T t) 소비 할 수 있는 인터페이스
Function<T, R> R apply(T t) 입력을 받아서 출력 할 수 있는 인터페이스
Predicate Boolean test(T t) 입력을 받아 참/거짓을 단정 할 수 있는 인터페이스
UnaryOperator T apply(T t) 단항(Unary) 연산 할 수 있는 인터페이스
BiConsumer<T, U> void accept(T t, U u) 이항 소비자 인터페이스
BiFunction<T, U, R> R apply(T t, U u) 이항 함수 인터페이스
BiPredicate<T, U> Boolean test(T t, U u) 이항 단정 인터페이스
BinaryOperator<T, T> T apply(T t, T t) 이항 연산 인터페이스

B.7. 컬렉션 스트림에서 람다 사용

람다는 다양한 용도가 있지만 그 중에서도 컬렉션 스트림을 위한 기능에 크게 초점이 맞춰져 있다. 컬렉션 스트림과 람다를 통해 더 적은 코드로 더 안정적인 코드를 만들 수 있다.

  1. for each 구문을 사용해 배열 첨자를 사용하지 않는 코드로 변경할 수 있다.
  2. 스트림을 사용하는 코드로 더 간소화할 수 있다.
    • How가 아닌 What만을 지정하게 해준다.
    • 함수형 프로그래밍의 장점인 선언적 프로그래밍을 활용하는 것.
    • SQL 구문과 유사하다는 특징이 있음.
    • 메서드 체인 패턴을 이용해 최종 연산이 아닌 모든 중간 연산은 다시 스트림을 반환해 코드를 간략하게 작성할 수 있게 지원한다.

B.8. 메서드 레퍼런스와 생성자 레퍼런스

프로그래머의 3대 스킬

  • C&P 넘어 CUP: Copy Understand Paste - 복사 이해 붙이기
  • D&C 넘어 DTC: Divide TDD Conquer - 분할 테스트 주도 개발 정복
  • C&I: Creative Idle - 창조적 게으름

메서드 레퍼런스의 세 가지 유형

  • 인스턴스::인스턴스메서드
  • 클래스::정적메서드
  • 클래스::인스턴스메서드

B.9. 인터페이스의 디폴트 메서드와 정적 메서드

B.10. 정리

[위로](# 목차)