。゚(*´□`)゚。

코딩의 즐거움과 도전, 그리고 일상의 소소한 순간들이 어우러진 블로그

[네이버클라우드] 클라우드 기반의 개발자 과정 7기/웹프로그래밍

[NC7기-47일차(6월30일)] - 웹프로그래밍 28일차

quarrrter 2023. 6. 30. 16:29

32. 객체 출력하기 / ObjectOutputStream, ObjectInputStream

인스턴스를 통째로 입출력하기(객체 직렬화) // ObjectOutputStream

write object 

단점: 출력 형식을 따라야함. 

 

Board 객체를 writeObject()로 입력하면 ObjectOutputStream이 byte[] 로 만들어서 파일에 저장함

*Board : 인스턴스 = 객체 

*인스턴스(byte[]) 를 일련의 바이트 정보로 바꾸는 것: serialize (직렬화) (=marshaling)

*byte[] : 인스턴스 필드값  + 클래스 정보(어느 클래스의 인스턴스인지)

 

형식별로 write 하던걸 그냥 Object로 퉁 ~

out.writeObject(board);

 

ObjectOutputStream으로 출력한건 

ObjectInputStream클래스를 사용해서 읽어야함 

파일의 byte[]을 readObject()로 읽어서 Board객체로 만들어줌 

byte[]를 인스턴스로 만드는 것: deserialize (역직렬화) (=unmarshaling)

*Board : 인스턴스 = 객체 

 

**************

this를 안 쓴다는 것: 인스턴스변수를 안 쓴다는 것. 그럼 통상 static으로 만들면 됨 . 

하지만 실무에선 언젠가 인스턴스를 사용할수있으니까 그냥 논스태틱으로 (인스턴스 멤버로) 둔다. 

 

인터페이스는 다중구현이 가능하다.

클래스는 다중상속이 안 되지만 ,, 

**************

 

* java.io.Serializable 인터페이스 

writeObject(인스턴스)         입력      --> ObjectOutputStream  
인스턴스: 직렬화가 허용된 클래스에 대해서만 serialize 가능 ! 
=> 직렬화 허용 : java.io.serializable 인터페이스구현한 클래스
=>serializable : 메서드가 없다, 직렬화를 허락한다는 표시용 /버전 같이 기재 - 나중에 읽을 때 현재 내가 갖고있는 클래스의 버전과 데이터에 저장된 데이터 버전을 비교하는 용도로 사용 . 
public class Member implements Serializable {
private static final long serialVersionUID = 1L;


*직렬화는 허락 받아야할 일인가? 
yes ! 
객체의 내용을 그대로 외부로 내보낸다는 것은 보안을 위협하는 행위이기 때문
   

 

*serialVersionUID 스태틱 필드

private static final long serialVersionUID = 1L;

Member - name, email, password, gender, createdDate 

wirteObject()로 출력 하면(serialize) byte[]이 파일에 저장됨 . 

파일을 byte[]로 읽어서 readObject()로(deserialize) 다시 Member객체로 만듬

 

1번 ok

홍길동컴으로 write ( serialVersion 1)  / Member - name, email, password, gender, createdDate 

파일 ( serialVersion 1) 

임꺽정컴으로 read ( serialVersion 1) / Member - name, email, password, gender, createdDate 

 

2번 오류 (읽어드릴 데이터의 버전이 다름 - 못 읽음)

이전 버전으로 serialize한 데이터를 새 버전의 인스턴스로 읽어들이기 싫다면 버전 번호를 달리 하라!

홍길동컴으로 write ( serialVersion 1)  / Member - name, email, password, gender, createdDate 

파일 ( serialVersion 1) 

임꺽정컴으로 read ( serialVersion 2) / Member - name, email, password, gender, createdDate, +postNo, +Adress +Tel

 

3번 ok (읽어드릴 데이터의 버전이 다름)

이전 버전으로 serialize한 데이터를 새 버전의 인스턴스로 읽어도 된다면 버전번호를 같게 하라! 

홍길동컴으로 write ( serialVersion 1)  / Member - name, email, password, gender, createdDate 

파일 ( serialVersion 1) 

임꺽정컴으로 read ( serialVersion 1) / Member - name, email, password, gender, createdDate, +postNo, +Adress +Tel

새로 추가된게 있어도 기존 내용에 추가된거니까 단순 업그레이드니까 버전 1로 그대로 

 

 


CSV 형식으로 머하기 

 comma seprated Value,,,

 


텍스트포맷(csv)로 입출력하기

 Board --prinf()--> prinWriter  --write--> BufferedWriter --writer--> FileWriter   ----> csv 파일에 저장완료! 

csv 파일 : 0,0,0,0 / 0,0,0,0

csv파일 --> Filereader --> BufferedReader --read()--> String: "0,0,0,0" --split(",")--> String배열에 쪼개 넣고 --set() -->  board객체에 넣음 

 


Refactoring 

1. Information Expert

App - > Board

1. board에서 get해서 

2. csv 문자열을 뽑아냄 "0,0,0,0,0" <= Board 객체의 값을 가지고 csv 형식의 문자열을 생성하는 것을 App클래스가 하고있음. => Data를 갖고 있는 건 Board인데 CSV형식으로 문자열을 만드는 일을 하는 객체는 App이다. 

==> Board의 필드가 추가되거나 삭제되면 App클래스를 변경해야한다. 

=> Information Expert 패턴으로 전환할 필요가 있다. 

 

 

:1.App이 Board에게 csv스트링 조. 

2.Board가 csv 문자열을 생성한다. 

3. csv 문자열을 리턴한다. 정보를 갖고있는 객체가 정보를 가공하는 일을 하도록 설계 변경! (information expert)

==> Board의 필드가 추가되거나 변경되더라도 App 클래스에는 영향을 주지 않는다. 

 


1. 제네릭 문법:  ?

private void saveCsv(String filename, List<? extends CsvObject> list)

?  : csvObject의 서브 클래스거나 csvObject의 구현클래스 

?에 올수 잇는거 (= list에 담을 수 있는 것, list 구현체)

: new ArrayList<Member>() ook

new LinkedList<Board>()  ok

new ArrayList<String>() 안됨 => String 클래스는 CsvObject 의 서브 클래스가 아니고 CsvObject 인터페잉스를 구현하지 않아서. 

CsvObject 수퍼클래스 or 인터페이스 

extends 인터페이스든 수퍼클래스든 다 extends 쓰기. implements 말고~ ~


2. Factory Method (GoF) 패턴 적용

CSV 문자열 -> App 이 분석해서 -> new 객체생성 (Board or Member)

Information Expert 적용(데이터를 갖고 있고 csv 형식을 알고 있는 클래스가 인스턴스를 생성하는게 바람직하다)

 

CSV 문자열 -> App 이 분석해서 ->fromCsv() 객체생성 (Board or Member)

fromCsv(): 인스턴스 생성하는 메서드: 인스턴스 생성과정이 복잡할 때 인스터너스 생성코드를 캡슐화(메서드로 묶기)하는게 낫다. => Factory Method (GoF) 패턴

 

Reflection API를 사용하여 메서드를 찾고 호출하기  + Generic 을 사용하여 다양한 타입에 대응할 수 있는 메서드를 정의. 

<T> void loadCsv(String filename, List<T> list, class<T> clazz) {

              Method factoryMethod = clazz.getDeclaredMethod(""fromCsv", String.class, String.class);

              (T) factoryMethod.invoke(null, line);

<T>:T가 타입 파라미터임을 선언!

 Method factoryMethod : 메서드 정보를 다루는 객체 

getDeclaredMethod: 현재 클래스에서 정의된 메서들 찾는다. "fromCsv" : 메서드 명 , String.class : 파라미터 타입 , 

(T) 리턴 값 형변환 / invoke : 메서드 호출 (null: 인스턴스 주소, 스태틱 메서드인 경우 null을 넘긴다. line ( 메서드를 호출할때 넘겨줄 파라미터 값)

 


 

getDeclaredMethod :

DeclaredMethod: 현재 클래스에 특정 이름을 갖는 메서드를 찾아줌, 못 찾으면 예외 발생 

List: 목록을 다루는 클래스 : collection API

Method factoryMethod = clazz.getDeclaredMethod("fromCsv", String.class);

파라미터를 String class로 받는 fromCsv이름을 가진 클래스를 ,,, 찾음 ,,, factoryMethod 에 담음 

Class : 메서드, 클래스, 생성자 정보를 다루는 클래스: reflection API