2022-01-25(화) 11주차 2일

매번 save() 해주는 거 귀찮음

save()는 내부적으로 호출할 거니까 private

save() private으로 바꾸기

private void save() throws Exception

생략된 this 다시 쓰는 중..

// variables initializer
ArrayList boardList = new ArrayList(); // 변수 선언 = 변수를 만들라는 명령!

생성자가 하나도 없으면 컴파일러는 기본 생성자를 자동으로 만들어준다.

ArrayList boardList = new ArrayList();

public CsvBoardDao() {
  super();
}

제일 첫 번째 문장에 슈퍼클래스의 생성자를 호출한다.
그 다음에 변수 초기화 문장의 뒷부분이 삽입된다.

ArrayList boardList;

public CsvBoardDao() {
  super();
  boardList = new ArrayList();
}

변수 초기화 문장이 먼저 실행된다는 말이 나오게 된 게
생성자 앞부분에 삽입되기 때문에

this 생략
원래는 적어야 하는 건데 안 적으면 컴파일러가 자동으로 붙인다.

규칙이 있음
만약에 boardList가
색깔이 바뀜
무조건 this.을 붙이는 게 아니라
로컬 변수가 아닐 때만
로컬 변수가 아니면 컴파일러가 자동으로 붙인다
this.을 생
인스턴스 주소 없이는 인스턴스 주소에 찾아갈 수 없다

성능 신경 쓰지 말기
DAO
왜 우리는 Controller에서 DAO를 분리시키는가? 여기에 중점두기

save()는 인스턴스 메서드니까 굳이 this. 안 써도 됨

인스턴스 메서드는 항상 인스턴스 주소를 줘서 유도한다.

DAO 그쪽으로 오류를 전달하는 게 맞음
BoardController가 처리할 문제임

update()에도
업데이트 하다가 오류났다.. 너가 처리해

delete()도

예외가 발생할 수 있는데 어쩔래
스프링 부트 알아서 하라고 해
throws Exception 추가

↓ BoardController에서 save() 메서드 지우기

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    boardDao.save();
    return boardDao.countAll();
  }

90-MyList프로젝트1 / 00 페이지

① 요청 → BoardController 요청 처리 → ② call →
→ CsvBoardDao 데이터 처리

바뀐 조회수가 저장이 안 되어 있음..
findByNo() 할 때도 save() 해야됨
조회수 업데이트
BoardController에서 조회수 카운트 하는 거 CsvBoardDao로 넘기자
save는 CsvBoardDao의 몫이다
데이터 처리는 무조건 CsvBoardDao에게 맡긴다.

// BoardController
  @RequestMapping("/board/get")
  public Object get(int index) {
    Board board = boardDao.findByNo(index);
    if (board == null) {
      return "";
    }
    board.setViewCount(board.getViewCount() + 1);
    return board;
  }
// CsvBoardDao
  public void increaseViewCount(int no) throws Exception {
    Board board = findByNo(no);
    board.setViewCount(board.getViewCount() + 1);
    save();
  }
// BoardController
  @RequestMapping("/board/get")
  public Object get(int index) throws Exception {
    Board board = boardDao.findByNo(index);
    if (board == null) {
      return "";
    }
    boardDao.increaseViewCount(index);
    return board;
  }

책, 블로그 보고 모방해서 적용하는 쪽에 초점 맞추기
모방이 어려운 거
실무에서 환경이 약간씩 다르기 때문에 가져와서 바꿔야 됨
신입 때 모방이라는 게 굉장히 힘든 거
깎기도 하고 붙이기도 하고 틈 사이에 넣어야 됨

원래 있던 데이터를 바꾸는 건 DAO에 맡긴다.

조회수 바뀐 거 잘 저장되는 거 확인함!

CSV의 한계점이 있다.

사진 데이터를 저장
CSV는 텍스트
사진 데이터를 저장 못 함
이제 바이너리 형식으로 저장해야겠구나
고객사에 납품할 때는 바이너리 형식으로 저장하는데
전에는 BoardController를 뜯어 고쳤음
이미 여러 고객사에 납품했고 CSV 형식으로
신규 고객사는 바이너리 형식으로
기존 고객은 내비두고
어떤 고객사는 CSV로 저장할 필요가 있
데이터 처리 코드를 별도 클래스로 분리시키게 되면 이 코드를 따로 재사용할 수 있고 대체할 수 있다. 만약에 아직까지도 BoardController에서 입출력을 했다면 BoardController를 뜯어 고쳐야 한다.
기존 코드를 재사용 못 함.
CsvBoardDao를 만들어
버릴 필요 없이 새로운 클래스를 만들면 됨!
이 맛에 분리시킴
역할을 쪼갤수록 부품 단위로 재사용하기 편하다

2단계 - 데이터를 저장할 때 CSV 포맷 대신 바이너리 형식으로 저장한다.

BinaryBoardDao 클래스 생성
인스턴스의 각 필드를 바이트 배열로 저장한다.

DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("boards.bin")));

읽을 때 쓰는 순서로 읽는다

board.setCreatedDate(Date.valueOf(in.readUTF()));

↑ 팩토리 메서드

파일명 같은 건 인스턴스 변수에 준비

String filename = "boards.bin";

out.writeUTF(board.getCreatedDate().toString());

out.flush();

BoardController를 변경하긴 변경해야됨
예전처럼 많이 변경하진 않음

CsvBoardDao를 BinaryBoardDao로 교체한다.

여기만 바꾸면 됨

2교시 시작

데이터 처리를 분리시키니까 좋다

3단계 - 데이터를 저장할 때 serialize/deserialize 방식을 사용한다.

SerialBoardDao 클래스 생성

인스턴스를 통째로 직렬화하여 저장한다.

기존 BinaryBoardDao 대신 SerialBoardDao를 호출하도록 한다.

String filename = "boards.ser";
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename)));

왕창 읽어들인다

ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));

개성을 살린다고 개발자 맘대로 메서드명 변경
메서드명 바꾸기
insert() → create()
update() → modify()
delete() → remove()

BinaryBoardDao를 SerialBoardDao로 교체한다.

기존의 DAO와는 메서드 이름이 다르기 때문에 그에 맞춰 코드를 변경해야 한다.

BinaryBoardDao 대신 SerialBoardDao로 교체하면
메서드명이 다르기 때문에 이전보다 더 많은 코드를 변경해야 한다.
(CsvBoardDao에서 BinaryBoardDao로 교체)
메서드명을 기존과 다르게 함
→ 호출방식이 다 달라진다.
→ 일관성이 깨진다.
⟹ 유지보수가 어려워진다.

DAO 구현과 프로그래밍 일관성

막을 방법이 없음

인터페이스 등장

객체 사용 규칙을 정의 - 인터페이스: 프로그래밍의 일관성을 지원하는 문법

인터페이스: 프로그래밍의 일관성을 지원하는 문법

인스턴스의 필드를 사용하는 방법
인스턴스 필드를 가지고 메서드를 실행하는 방법

BoardController

4단계 - 인터페이스로 DAO 사용 규칙을 정의한다.

• mylist.dao.BoardDao 인터페이스 정의
‐ DAO 객체에 대해 호출하는 메서드의 시그너처를 정의한다.
‐ 일관된 호출이 가능
• mylist.dao.XxxDao 클래스 변경
‐ BoardDao 규칙에 따라 클래스를 작성한다.

caller를 작성하거나 callee를 작성
지금처럼 인터페이스를 작성하고 호출하는 caller도 작성하고 사용되는 callee도 작성하는 3가지를 한꺼번에

보통의 경우는 둘 중 하나
그 규칙에 따라서 호출하는 caller를 짜거나

3가지를 다 만들어보자

인터페이스 ← 객체 사용 규칙을 통일하기 위해서

08.1 DAO 역할 도입 : 인터페이스 도입하여 DAO 사용 규칙을 통일하기

인터페이스 : 객체 사용 규칙을 정의하는 문법
메서드 호출 규칙
동일한 이름으로 메서드를 호출할 수 있어서
일관성이 있다
동일한 기능에 동일한 규칙에 따라서 메서드를 만들기 때문에
프로그래밍에 일관성을 제공
호출하는 쪽과 메서드를 만드는 쪽 클래스를 만드는 쪽

장점 : 교체하기 쉽다

기존의 XxxBoardDao를 인터페이스 규칙에 따라 변경하도록 한다.

package com.eomcs.mylist.dao;

import com.eomcs.mylist.domain.Board;

public interface BoardDao {

  // 인터페이스는 객체의 메서드 호출 규칙을 정의하는 것이기 때문에
  // 메서드를 작성할 때 메서드 몸체(method body)를 작성하지 말아야 한다.
  // 메서드 바디가 없는 메서드를 "추상 메서드(abstract method)"라 부른다.

  int countAll();

  Object[] findAll();

  void insert(Board board) throws Exception;

  Board findByNo(int no);

  int update(int no, Board board) throws Exception;

  int delete(int no) throws Exception;

  void increaseViewCount(int no) throws Exception;
}

public 생략 가능

SerialBoardDao 수정하기

BoardDao boardDao = new CsvBoardDao();

그 인터페이스 규칙에 따라서 만든 클래스라면 그 클래스의 객체 주소를 담을 수 있다.
BoardDao 규칙에 따라서 만든 객체를 담는다.
통상적으로 뒤에 접미사를 인터페이스 이름으로 붙인다.
또는 BoardDaoImpl() 이렇게 하는 경우도 있음
2가지 다 쓰임

✔ 인터페이스 문법을 활용하면 객체의 교체가 쉬워진다.

여기만 바꾸면 됨

4단계 - JSON 형식으로 데이터를 저장하고 읽는 DAO를 인터페이스 규칙에 따라 작성한다.

  • com.eomcs.mylist.dao.JsonBoardDao 클래스 생성
  • BoardDao 인터페이스 규칙에 따라 작성한다.
BufferedWriter out = new BufferedWriter(new FileWriter(filename));

5단계 - BoardDao 구현체를 Spring Boot가 주입하도록 변경한다.

‐ 클래스 선언부에 @Repository 애노테이션을 붙인다.

BoardDao 필드 선언에 @Autowired 애노테이션을 붙인다.

필드 선언부에 이 애노테이션을 붙여서 표시해 두면,
Spring Boot가 실행될 때 BoardController 객체를 만들 때 BoardDao 구현체를 찾아 자동으로 주입한다.

아까보다 더 편함
아까는 BoardController 변경했어야 했음

BoardController 코드를 손대지 않고
기존 코드를 손대지 않고 객체를 교체시킬 수 있다.

의존객체 주입받기 (Dependency Injection)

의존 객체 주입 - Dependency Injection(DI)

Bean Container = IoC Container = DI Container
Bean : 자바 객체 = 인스턴스
Container : 객체 생성, 보관, 삭제 → 객체 관리
­+ 의존 객체 자동 주입
= DI Container (IoC 기법 중의 하나)
= IoC Container
IoC : Inversion of Control (제어의 역행)
보통 : 사용할 객체를 직접 생성
역행 : 사용할 객체를 주입 받는다

🔹 IoC 예
1. DI
2. Event Handler(Listener)
callback 방식

내가 만든 메서드를 내가 호출하는 게 아니라 딴 놈이 호출
내가 만든 함수를 내가 호출
제어가 평범하지 않음
거꾸로임

외부에서 객체를 주입하게 되면

의존 객체 주입 방식을 적용하게 되면
① 객체를 교체하기 쉽다.
② 객체 관리도 쉽다. ← IoC Container가 한다.

https://github.com/spring-projects/spring-framework

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

추상 클래스
처음부터 추상 클래스를 정의한 후에

추상 클래스

🔹 추상 클래스
서브 클래스가 가져야 할 반드시 가져야 할 공통 분모를 정의한 클래스
보통 상속의 generalization 과정에서 식별한다.

<<interface>> BoardDao

<<abstract>> AbstractBoardDao

08.3 DAO 역할 도입: 상속의 generalization을 수행하여 추상 클래스 정의하기

1단계

AbstractBoardDao 클래스 생성

public abstract class AbstractBoardDao implements BoardDao {

모든 서브클래스의 공통 분모

파일을 다루는 건 수퍼클래스가 할 일이 아님

서브클래스마다 save() 방법이 다 다름
다른데 반드시 있어야 함

2단계 - XxxBoardDao 클래스의 수퍼 클래스를 AbstractBoardDao로 변경한다.

중복되는 코드를 최소화시킨다.

public class BinaryBoardDao extends AbstractBoardDao {

계층 난이도가 높아짐

처음부터 이렇게 짤 생각하지 말아라

처음은 단순하게 시작

리팩토링 ← 코드 정리

3단계 - BookController, ContactController, TodoController 클래스에서 데이터 처리 코드를 DAO 클래스로 분리한다.

3단계 - BookController, ContactController, TodoController 클래스에서 데이터 처리 코드를 DAO 클래스로 분리한다.

좋은 웹페이지 즐겨찾기