07.4 파일 API 활용 : 의존 객체 주입 방식으로 기능 확장

oop.ex05.x1

기능 확장을 위한 다양한 기법 (oop.ex05.x1)

03-OOP1 / 86 페이지

Calculator

① 기존 클래스에 코드 추가 (oop.ex05.x1.upgrade1)
‐ 기존 클래스를 이용하고 있는 애플리케이션에 영향 끼침
‐ 기존 코드를 손대면 → 오류 발생 ↑

② 기존 클래스 복제한 후 코드 추가 (oop.ex05.x1.upgrade2)
‐ 애플리케이션마다 중복 코드 생성
변경 사항 발생 → 번거롭다

③ 기존 클래스를 상속 받은 후에 코드 추가 (oop.ex05.x1.upgrade3)
‐ 기존 코드의 소스파일이 없어도 된다.
‐ 기존 코드를 손대지 않는다. → 기존 코드에 버그를 추가하는 위험성이 없다.
‐ 기존 코드가 변경되면 → 즉시 서브 클래스에 변경사항이 적용
‐ 일부 기능만 상속받는 것이 불가! → 코드 관리 유연성이 떨어진다. (경직되어 있다.)

설치형은 무조건 해당 파일 교체
고객사가 서버에 접속해서
클라우드 소프트웨어로
SaaS(Software-as-a-Service)

배달의 민족은 두 가지 다 제공
설치형 어플 설치해야 됨
주문, 결제는 서버에서
결국 웹 프로그램
iOS 전용
안드로이드 전용
플러터 OK

④ 포함관계를 통해 기능 확장 (oop.ex05.x1.upgrade4)
Calculator ←◆ Calculator2
‐ 상속에 비해 유지보수가 유연하다. → 필요한 객체만 포함할 수 있다.

🔹 Composition 관계
Calculator2 ⩵ Calculator ← LifeCycle이 같다
예) 사람과 심장 관계, 자동차와 엔진 관계
연결이 강하게 되어 있다.

⑤ 의존 객체 주입받기 (oop.ex05.x1.upgrade5)

03-OOP1 / 88 페이지

🔹 Aggregation 관계
Calculator2 ≠ Calculator ← LifeCycle이 다르다.
따로 만들어진다.
예) 자동차와 블랙박스 관계
자동차가 만들어질 때 블랙박스가 만들어지는 게 아님

🔹 의존객체 주입받기 (Dependency Injection)
‐ 의존 객체 : 작업할 때 사용하는 객체
예) 핸드폰, 돈

요리사는 칼을 외부에서 받아 쓴다.
작업하려면 칼이 필요한데 칼을 외부에서 받는다. (주입받는다.)
요리사는 양념이 필요하다.
비빔밥을 만들어야 됨.
양념이 필요한데 양념은 누가 만들죠?
양념을 주입받을 수 있고 요리사가 만들 수도 있음.
자체적으로 생성하는 경우가 있고 칼처럼 주입받는 경우가 있음.
비빔밥 만드는 작업을 하려면 양념이 필요하고 칼이 필요함.
쌀은 자기가 농사를 지을 순 없음. 주입받아야 됨.
어떤 객체가 일을 하는데 자기가 자체적으로 생성해서 쓸거냐 외부에서 주입받아서 쓸거냐
자기가 자체적으로 만들면 장점도 있는데 단점이 있음
칼도 종류가 많음..
외부에서 주입받으면 상황에 따라서 다양한 칼을 주입받을 수 있음
자기가 자체적으로 하면 자기가 일일이 만들어야 됨
외부에서 주입을 받으면 훨씬 더 유연해짐
상황에 따라서 여러 종류의 칼을 주입받을 수 있음
뭘 주입하느냐에 따라서 작업하기가 편해짐.
자기가 만들어 쓰면 과도칼도 만들어야 되고 다른 칼도 만들어야 되고 칼도 갈아야 되고
교체하기 편하게 바깥에서 만들어서 주입하는 방법을 쓴다

🔹 주입
사용할 객체를 설정하는 것
‐ 생성자에서 설정하는 법
‐ setter에서 설정하는 법

의존 객체 자동화
의존 객체 주입을 자동화시키는 걸 Dependency Injection(DI)라고 한다.
줄여서 DI라고 부른다.
Dependency Injection을 도와주는 도구를 DI Container라고 부른다.
또다른 말로 IoC Container라고 부른다.
둘 다 객체를 생성을 해서
둘 다 Bean Container다.
대표적인게 바로 Spring IoC Container이다.
Spring IoC Container 를 포함하는 게 Spring Boot이다.

2교시 시작

upgrade4와 upgrade5의 차이
자체적으로 생성하느냐 외부에서 주입받으냐 차이

외부에서 주입받게 되면 여러 가지 넣을 수 있다

Calculator 예제는 여기가 한계

의존 객체 주입의 장점을 극대화시키기에는 보여주기에는 여기가 한계

com.eomcs.oop.ex05.x2 생성하기

oop.ex05.x2

클래식한 방법에서 노멀한 방법
클래스에서 노말한 방법 상속
어드밴스드 방법

Calculator 의존 객체 주입의 장점을 설명하기에 적합하지 않았음

왜 상속이 의미가 있고 오버라이딩이 왜 의미가 있고 의존 객체를 생성자로 받는 게 왜 의미가 있는지

불편함을 느끼라고

기존 코드를 계속 손대면서 계속 덕지덕지 붙어가면서 클래스가 점점 무거워지고 있음

새로 메서드를 추가하는 경우도 있지만 때에 따라서는 기존 코드를 변경해야 되는 상황이 반드시 온다

단순히 변수나 메서드를 추가한다는 게 아니라 기존 코드를 손대는 상황이 온다

몇천라인이라면 거기에 얼마나 많이 손을
기존 코드가 안 돌아감

기능 확장 방법1 - 기존 코드에 새 기능 추가

03-OOP2 / 1 페이지

자동차 기능 추가
start()
stop()

트럭 기능 추가
dump()

트레일러 기능 추가
trailer
setTrailer()

하이브리드 전기차 기능 추가
kwh
chargeBattery()

애플리케이션을 만들 때마다 Engine 클래스에 계속 기능 추가
⟹ 한 클래스가 많은 역할을 하게 된다
트럭 기능만 필요한 애플리케이션을 만드는 입장에서는 불필요한 기능까지 가져가야 한다.
⟹ 유지보수에 안 좋다
기존 코드를 변경하다 보면 버그가 추가될 가능성이 높아진다.

장점도 있음.
Engine 클래스에 계속 추가한 거라 Engine 클래스만 수정하면 됨.

com.eomcs.oop.ex05.x3 생성하기

3교시 시작

oop.ex05.x3

기능 확장 방법2 - 복제한 코드에 새 기능 추가

03-OOP2 / 2 페이지

① 자동차 만들기 (app1)
x3.Engine 복제
자동차 기능 추가
start()
stop()

② 트럭 만들기 (app2)
x3.app1.Engine 복제
트럭 기능 추가

③ 캠핑카 만들기 (app3)
CampingTrailer 클래스 정의
x3.app1.Engine 복제
트레일러 연결 기능 추가

④ 하이브리드 전기차 만들기 (app4)
x3.app1.Engine 복제
하이브리드 자동차 기능 추가

복제한 코드를 하나하나 수정하다보면 사람이기에 실수함
변경하는것조차 실수함

❌ 문제점
‐ 기존 코드를 변경하면 그 코드를 복제해서 만든 모든 코드를 찾아서 변경해야 한다.
‐ 사람이기에 실수를 한다! 잘못 변경할 수 있다.

com.eomcs.oop.ex05.x4 생성

oop.ex05.x4

기능 확장 방법3 - 상속 문법을 활용

03-OOP2 / 3 페이지

class 파일만 있으면 됨

자바의 모든 클래스가 상속 관계

상속의 장점, 단점 경험하고 새로운 기능 확장 기법을 배운다

4교시 시작

app1 : 자동차 기능 추가
x4.Engine 상속
Car extends Engine
start() 추가
stop() 추가

app2 : 트럭 기능 추가
Truck extends Car
Car를 상속받아서 Truck 만들기
dump() 기능 추가

재사용 → 비용 절감

app3 : 트레일러 기능 추가
CampingTrailer 클래스 만들기
TrailerCar 클래스 만들기
TrailerCar extends Car

app4 : 하이브리드 기능 추가
HybridCar 클래스 만들기
HybridCar extends Car

코드가 중복이 안 된다

✔ 수퍼 클래스의 기능을 변경하면 서브 클래스에 즉시 반영된다.

수퍼 클래스 Engine.java 하나만 바꿔주면 다 반영됨

ex05.x2 : 한 클래스에 기능 다 합친 거
ex05.x3 : 복제해서 기능 추가한 거
ex05.x4 : 상속 문법을 사용해서 기존 코드 손대지 않고 기능 추가 + 메서드 오버라이딩

컴파일러는 문법적으로만 따진다

A 클래스에 존재하지 않는 메서드면 컴파일 에러

3시 40분까지 하기

5교시 시작

기능 확장 방법3 - 상속을 통한 기능 확장의 한계

03-OOP2 / 4 ~ 6 페이지

ex05.x4.app5

✅ 요구사항
전기 트럭을 만들라!

자바는 다중 상속 불가

Truck을 상속받거나 HybridCar를 상속받아야 됨

코드 중복 발생

HybridTruck 클래스 만들기
HybridTruck extends HybridCar
트럭 기능 추가

✅ 요구사항
트레일러를 붙일 수 있는 트럭을 만들라!

TrailerTruck 클래스 만들기
TrailerTruck extends Truck
트레일러 붙이는 기능 추가

✅ 요구사항
전기 캠핑카를 만들라!

HybridTrailerCar 클래스 만들기
HybridTrailerCar extends TrailerCar
하이브리드 자동차 기능 추가

✔ 이렇게 기능 조합을 하다 보면 수많은 서브 클래스가 생성된다.

✔ 상속으로 다양한 기능이 조합된 객체를 만들기 번거롭다.

✔ 유지보수가 어려워진다.

설계 방법을 바꾸자..

oop.ex05.x5

기능 확장 방법4 - 데코레이터 설계 기법 이용

03-OOP2 / 7 페이지

주객체와 옵션(선택기능)을 나눈다.

🔹 주객체
Car
Truck

🔹 선택 기능
Gas Engine
ElectricEngine
Trailer

<<abstract>> Car
                 Sedan
                 Truck
   <<Decorator>> Option 
                        Trailer
                        ElectricEngine    

옵션은 자동차에 붙이는 거
자동차를 포함한다
자동차가 옵션을 포함하는 게 아니라 옵션이 자동차를 포함

The type Sedan must implement the inherited abstract method Car.run()

Option extends Car

Option은 반드시 Car를 포함해야 한다.

어느 자동차에 붙일 건지 Car 주소를 받아야 됨

사용하려면 진짜 오리지널 객체를 껍데기에 끼워야 됨

Trailer extends Option

ElectricEngine extends Option

옵션 자체는 실제 자동차가 아니다. 그래서 run()이 호출되면 실제 자동차 객체에게 위임한다.

Trailer trailer = new Trailer(sedan);
트레일러 기능이 추가된 세단이 된다

Trailer trailer2 = new Trailer(truck);
트레일러 기능이 추가된 트럭이 된다


트레일러를 떼고 싶어도 못 뗌

지금
트레일러를 붙이고 싶으면 붙이고 떼고 싶으면 떼면 됨

옵션을 붙일 때도 순서가 있음

옵션에 따라 순서가 바뀐다

package com.eomcs.oop.ex05.x5;

public class CarTest3 {
  public static void main(String[] args) {
    Sedan sedan = new Sedan();

    ElectricEngine electricOption = new ElectricEngine(sedan);
    electricOption.chargeBattery(100);

    Trailer trailer = new Trailer(electricOption);
    trailer.start();
    trailer.run();
    trailer.stop();

    System.out.println("----------------------------");

    Truck truck = new Truck();
    Trailer trailer2 = new Trailer(truck);
    trailer2.start();
    trailer2.run();
    trailer2.stop();
  }
}
package com.eomcs.oop.ex05.x5;

public class CarTest3 {
  public static void main(String[] args) {
    Sedan sedan = new Sedan();

    ElectricEngine electricOption = new ElectricEngine(sedan);
    electricOption.chargeBattery(100);

    Trailer trailer = new Trailer(electricOption);
    trailer.start();
    trailer.run();
    trailer.stop();

    System.out.println("----------------------------");

    Truck truck = new Truck();
    ElectricEngine electricOption2 = new ElectricEngine(truck);
    electricOption2.chargeBattery(300);
    electricOption2.start();
    electricOption2.run();
    electricOption2.stop();
  }
}

상속의 한계... 수많은 서브클래스가 생성된다
그래서 등장한 게 데코레이터 설계 기법
주객체에 붙인다

new 데코레이터(주객체)

new 옵션(주객체)

new 옵션3(new 옵션2(new 옵션1(주객체)))

new 옵션3(객체)

⟹ 기능을 선택적으로 추가할 수 있다!

07.4 데코레이터 패턴 활용한 읽고 쓰기 기능 확장

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

① 07.1
FileReader + 한 줄 단위로 데이터 읽기

② 07.2 상속
FileReader 상속 받아서 FileReader2 만듦
readLine() 추가

③ 07.3 위임
FileReader 포함해서 FileReader2 만듦
readLine() 추가

④ 07.4 데코레이터
FileReader 포함
BufferdReader 클래스 사용
BufferdReader 생성자에 FileReader 객체를 가져오는 게 있음
얘가 데코레이터 역할
여기에 readLine() 메서드가 들어 있음

7.3 방법은 강하게 연결되어 있음

Decorator pattern 데코레이터 패턴

ConcreteComponent(주객체)

이클립스 단축키
ctrl + shift + o : 자동 import

FileReader에는 readLine() 메서드가 없다
BufferedReader 객체를 만들고 FileReader를 주객체로 넘긴다.
그럼 BufferedReader에 있는 readLine() 쓸 수 있다

  // Board 객체 목록을 저장할 메모리를 준비한다.
  ArrayList boardList = new ArrayList();

  public BoardController() throws Exception {
    System.out.println("BoardController() 호출됨!");

    // 1) 주 작업 객체(concrete component) 준비 
    FileReader in = new FileReader("boards.csv");

    // 2) 한 줄 단위로 데이터를 읽는 작업을 수행하는 데코레이터 준비
    BufferedReader in2 = new BufferedReader(in);

    String line;
    while ((line = in2.readLine()) != null) { // 한 줄의 문자열을 읽었으면
      boardList.add(Board.valueOf(line)); // 파일에서 읽은 한 줄의 CSV 데이터로 객체를 만든 후 목록에 등록한다.
    }

    in2.close();
    // in.close(); // 데코레이터를 close() 하면 그 데코레이터와 연결된 객체들도 모두 close() 된다.
  }

데코레이터 패턴이 왜 등장했고 있으면 어느 점에서 편한지

oop.ex05.x2 ~ oop.ex05.x5 숙지하기
역사가 경험이 담겨 있다

FileReader에는 한 줄 단위로 읽는 기능이 없음
BufferedReader를 붙여야 됨
생성자에 FileReader 객체를 넘겨주면 된다

FileReader는 파일에서 데이터를 읽는 일을 한다.
BufferedReader는 읽은 데이터를 한 줄의 문자열로 만들어서 한 줄을 다 읽으면 리턴하는 일을 한다.
BufferedReader는 파일을 읽을 수 없다. 그래서 주객체로 FileReader를 줘야 된다.
엔터를 만나면 버퍼에 담아놓은 걸 리턴한다.
한 줄 단위로 끊어서 리턴

파일 읽는 기능에 한 줄씩 리턴하는 기능을 덧붙인 거

BufferedReader.readLine() ← 더 이상 읽을 문자열이 없으면 null을 리턴함

while ((line = in2.readLine()) != null)

직접 안 쓰면 굳이 참조변수에 안 넣고 바로 넣는다

// 주 객체에 데코레이터 객체를 연결
BufferedReader in = new BufferedReader(new FileReader("books.csv"));

파일 읽는 기능에 한 줄씩 리턴하는 기능을 덧붙인 거

PrintWriter : printf(), println() 등등 있음
PrintWriter도 데코레이터

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    // 1) 주 작업 객체 준비
    FileWriter out = new FileWriter("boards.csv"); // 따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.

    // 2) 한 줄 단위로 출력하는 데코레이터 객체 준비
    PrintWriter out2 = new PrintWriter(out);

    Object[] arr = boardList.toArray();
    for (Object obj : arr) {
      Board board = (Board) obj;
      out2.println(board.toCsvString());
    }

    out.close();
    // out.close(); // 데코레이터에서 close()하면 그 데코레이터와 연결된 모든 객체도 자동으로 close() 한다.
    return arr.length;
  }
// 데코레이터 생성자에 주 객체를 바로 넘긴다.
PrintWriter out = new PrintWriter(new FileWriter("books.csv"));

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/PrintWriter.html#constructor.summary

FileWriter는 Writer의 서브 클래스

PrintWriter(Writer out)
Writer 및 Writer 서브 타입 객체도 다 올 수 있다는 의미
서브 타입도 올 수 있다

기능을 붙였다 떼었다를 자유롭게 하기 위해서

PrintWriter에는 오버로딩 메서드가 많다.

원래부터 자바에서 제공해주는 데코레이터 역할을 하는 클래스를 직접 써봤다.

수많은 클래스 중에서 누가 주객체고 누가 데코레이터입니까?

생성자에 다른 입출력 스트림 없이 스스로 생성할 수 있으면 주객체

데코레이터는 다른 객체에 붙여야 되기 때문에 반드시 생성자에 객체를 요구한다.

BufferedReader는 기본 생성자가 없다. 얘는 그냥 못 만든다.
Reader에 붙이는 거. 그래서 데코레이터.
스스로 못 만들어짐

CharArrayReader는 Reader가 아니라 char 배열
Reader를 요구하지 않는다. 주 객체다.

FileReader도 생성자에 다른 Reader를 요구하지 않는다. 주 객체다.

데코레이터 자신도 같은 조상을 갖고 있다.

public abstract class Option extends Car

public abstract class Option extends Car {
  Car car;

  public Option(Car car) {
    this.car = car;
  }
}

FileReader BufferedReader 둘 다 Reader의 자손이다.
그러면서 데코레이터는 Reader 객체를 포함한다.

FilterReader는 다른 Reader가 있어야 된다. 데코레이터다.

LineNumberReader도 스스로 객체를 만들 수 없다. 다른 Reader가 있어야 한다.

자기랑 같은 조상에 있는 Reader들을 생성자에서 받으면 데코레이터
안 받으면 데코레이터 아님

PrintWriter는 다른 Writer를 받는다. 데코레이터다.
근데 스스로 File을 출력할 수도 있다. 주 객체이다.
주 객체이면서 데코레이터로 쓸 수 있다.

가수.. 코러스..

오늘 배운 데코레이터를 MyList에 적용해보았다.

내일은 그 다음으로 가기 전에 파일입출력에 대해서 짚고 가겠다.

java.io 패키지에 있는 클래스들이 다 데코레이터 패턴이 적용된 상태
java.io 패키지에 있는 클래스를 그냥 사용하지 말고
아~ 데코레이터 패턴이 적용된 상태구나~
이게 바로 데코레이터 패턴이구나~
쓸 때마다 인지하고 쓰기

0개의 댓글