package com.bitcamp.util;
public interface Iterator<E> {
boolean hasNext();
E next();
}
public interface List<E> {
...
Iterator<E> iterator();
}
package com.bitcamp.util;
public abstract class AbstractList<E> implements List<E> {
...
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int index;
@Override
public boolean hasNext() {
return index < AbstractList.this.size();
}
@Override
public E next() {
return AbstractList.this.get(index++);
}
};
}
}
toArray() 대신, iterator() 사용을 통해 Iterator 객체 사용법 훈련.
(Iterator 사용하는 것이 필수는 아니다.)
package com.bitcamp.board.dao;
import com.bitcamp.board.domain.Board;
import com.bitcamp.util.Iterator;
import com.bitcamp.util.LinkedList;
import com.bitcamp.util.List;
// 게시글 목록을 관리하는 역할
//
public class BoardDao {
List<Board> list = new LinkedList<>();
...
public Board[] findAll() {
Iterator<Board> iterator = list.iterator();
// 역순으로 정렬하여 리턴한다.
Board[] arr = new Board[list.size()];
int index = list.size() - 1;
while (iterator.hasNext()) {
arr[index--] = iterator.next();
}
return arr;
}
package com.bitcamp.board.dao;
import com.bitcamp.board.domain.Member;
import com.bitcamp.util.Iterator;
import com.bitcamp.util.LinkedList;
import com.bitcamp.util.List;
// 회원 목록을 관리하는 역할
//
public class MemberDao {
List<Member> list = new LinkedList<Member>();
...
public Member[] findAll() {
Iterator<Member> iterator = list.iterator();
Member[] arr = new Member[list.size()];
int i = 0;
while (iterator.hasNext()) {
arr[i++] = iterator.next();
}
return arr;
}
}
package com.bitcamp.util;
public class Stack<E> extends LinkedList<E> {
...
@Override
public String toString() {
Iterator<E> iterator = iterator();
StringBuffer buf = new StringBuffer();
while (iterator.hasNext()) {
if (buf.length() > 0) {
buf.append(" > ");
}
buf.append(iterator.next());
}
return buf.toString();
}
}
public class App {
// breadcrumb 메뉴를 저장할 스택을 준비
public static Stack<String> breadcrumbMenu = new Stack<>();
...
- 데이터 조회 객체화: 데이터 조회와 관련된 필드와 메서드를 분리해서 외부 부품(객체, 클래스)으로 만든다.
⇒ 객체화 한다.
⇒ 유지보수를 위해서!!
⇒ 기능 변경 및 교체가 쉽다!
예; 원래 App 클래스에 모여있던 기능들을 여러개의 클래스로 나누었던 것 또한 객체화한 것이다!- 객체, 부품의 기본 단위: 클래스
util.List 인터페이스 삭제
util.AbstractList 추상 클래스 삭제
util.ObjectList 추상 클래스 삭제
util.LinkedList 추상 클래스 삭제
util.Stack 클래스 삭제
dao.BoardDao 클래스 변경
dao.MemberDao 클래스 변경
dao.BoardDao 클래스 변경
dao.App 클래스 변경
App → Handler → DAO → FileOutputStream → File
App class → BoardHandler class → BoardDao class → File 접근
Handler[] handlers = new Handler[] {
new BoardHandler("board.data"), // 게시판
new BoardHandler("reading.data"), // 독서록
new BoardHandler("visit.data"), // 방명록
new BoardHandler("notice.data"), // 공지사항
new BoardHandler("daily.data"), // 일기장
new MemberHandler("member.data") // 회원
};
handlers[mainMenuNo - 1].execute();
public class BoardHandler extends AbstractHandler {
private BoardDao boardDao;
public BoardHandler(String filename) {
// 수퍼 클래스의 생성자를 호출할 때 메뉴 목록을 전달한다.
super(new String[] {"목록", "상세보기", "등록", "삭제", "변경"});
boardDao = new BoardDao(filename);
try {
boardDao.load();
} catch (Exception e) {
System.out.printf("%s 파일 로딩 중 오류 발생!\n", filename);
}
}
...
}
private void onInput() throws Exception {
Board board = new Board();
board.title = Prompt.inputString("제목? ");
board.content = Prompt.inputString("내용? ");
board.writer = Prompt.inputString("작성자? ");
board.password = Prompt.inputString("암호? ");
board.viewCount = 0;
board.createdDate = System.currentTimeMillis();
this.boardDao.insert(board);
this.boardDao.save();
System.out.println("게시글을 등록했습니다.");
}
private void onDelete() throws Exception {
int boardNo = 0;
while (true) {
try {
boardNo = Prompt.inputInt("삭제할 게시글 번호? ");
break;
} catch (Exception ex) {
System.out.println("입력 값이 옳지 않습니다!");
}
}
if (boardDao.delete(boardNo)) {
this.boardDao.save();
System.out.println("삭제하였습니다.");
} else {
System.out.println("해당 번호의 게시글이 없습니다!");
}
}
private void onUpdate() throws Exception {
int boardNo = 0;
while (true) {
try {
boardNo = Prompt.inputInt("변경할 게시글 번호? ");
break;
} catch (Throwable ex) {
System.out.println("입력 값이 옳지 않습니다!");
}
}
Board board = this.boardDao.findByNo(boardNo);
if (board == null) {
System.out.println("해당 번호의 게시글이 없습니다!");
return;
}
String newTitle = Prompt.inputString("제목?(" + board.title + ") ");
String newContent = Prompt.inputString(String.format("내용?(%s) ", board.content));
String input = Prompt.inputString("변경하시겠습니까?(y/n) ");
if (input.equals("y")) {
board.title = newTitle;
board.content = newContent;
this.boardDao.save();
System.out.println("변경했습니다.");
} else {
System.out.println("변경 취소했습니다.");
}
}
@Override
public void service(int menuNo) {
try {
switch (menuNo) {
case 1: this.onList(); break;
case 2: this.onDetail(); break;
case 3: this.onInput(); break;
case 4: this.onDelete(); break;
case 5: this.onUpdate(); break;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
...
// 사용자가 입력한 메뉴 번호에 대해 작업을 수행한다.
service(menuNo);
...
} catch (Exception ex) {
System.out.printf("예외 발생: %s\n", ex.getMessage());
}
public class BoardDao {
...
public BoardDao(String filename) {
this.filename = filename;
}
public void load() throws Exception {
FileInputStream in = new FileInputStream(filename);
// FileInputStream 도구를 사용하여 파일로부터 데이터를 읽어 들인다.
// => 먼저 게시글 개수를 읽는다.
int size = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
for (int i = 0; i < size; i++) {
// => 파일에서 읽은 게시글 데이터를 저장할 객체를 준비한다.
Board board = new Board();
// => 저장된 순서로 데이터를 읽는다.
// 1) 게시글 번호 읽기
int value = 0;
value += in.read() << 24; // 예) 12 => 12000000
value += in.read() << 16; // 예) 34 => 00340000
value += in.read() << 8; // 예) 56 => 00005600
value += in.read(); // 예) 78 => 00000078
board.no = value;
// 2) 게시글 제목 읽기
int len = 0;
// 출력된 게시글 제목의 바이트 수를 읽어서 int 변수에 저장한다.
len = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
// 게시글 제목을 저장할 바이트 배열을 만든다.
byte[] bytes = new byte[len];
// 게시글 제목을 바이트 배열로 읽어 들인다.
in.read(bytes);
// 바이트 배열을 가지고 String 인스턴스를 생성한다.
board.title = new String(bytes, "UTF-8");
// 3) 게시글 내용 읽기
len = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
bytes = new byte[len];
in.read(bytes);
board.content = new String(bytes, "UTF-8");
// 4) 게시글 작성자 읽기
len = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
bytes = new byte[len];
in.read(bytes);
board.writer = new String(bytes, "UTF-8");
// 5) 게시글 암호 읽기
len = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
bytes = new byte[len];
in.read(bytes);
board.password = new String(bytes, "UTF-8");
// 6) 게시글 조회수 읽기
board.viewCount = (in.read() << 24) + (in.read() << 16) + (in.read() << 8) + in.read();
// 6) 게시글 등록일 읽기
board.createdDate =
(((long)in.read()) << 56) +
(((long)in.read()) << 48) +
(((long)in.read()) << 40) +
(((long)in.read()) << 32) +
(((long)in.read()) << 24) +
(((long)in.read()) << 16) +
(((long)in.read()) << 8) +
((in.read()));
// System.out.println(board);
// 게시글 데이터가 저장된 Board 객체를 목록에 추가한다.
list.add(board);
// 파일에서 게시글을 읽어 올 때는 항상 게시글 번호를 boardNo에 저장한다.
// 그래야만 새 게시글을 저장할 때 마지막 게시글 번호 보다 큰 값으로 저장할 수 있다.
boardNo = board.no;
}
in.close();
}
public void save() throws Exception {
FileOutputStream out = new FileOutputStream(filename);
// 첫 번째로 먼저 게시글의 개수를 4바이트 int 값으로 출력한다.
out.write(list.size() >> 24);
out.write(list.size() >> 16);
out.write(list.size() >> 8);
out.write(list.size());
for (Board board : list) {
// int ==> byte[]
// 예) board.no = 0x12345678
System.out.println("------------------------");
System.out.printf("%08x\n", board.no);
out.write(board.no >> 24); // 0x00000012|345678
out.write(board.no >> 16); // 0x00001234|5678
out.write(board.no >> 8); // 0x00123456|78
out.write(board.no); // 0x12345678|
// String(UTF-16) => UTF-8
System.out.printf("%s\n", board.title);
// 출력할 바이트 배열의 개수를 먼저 출력한다.
byte[] bytes = board.title.getBytes("UTF-8");
out.write(bytes.length >> 24);
out.write(bytes.length >> 16);
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
System.out.printf("%s\n", board.content);
bytes = board.content.getBytes("UTF-8");
out.write(bytes.length >> 24);
out.write(bytes.length >> 16);
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
System.out.printf("%s\n", board.writer);
bytes = board.writer.getBytes("UTF-8");
out.write(bytes.length >> 24);
out.write(bytes.length >> 16);
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
System.out.printf("%s\n", board.password);
bytes = board.password.getBytes("UTF-8");
out.write(bytes.length >> 24);
out.write(bytes.length >> 16);
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
// int ==> byte[]
System.out.printf("%08x\n", board.viewCount);
out.write(board.viewCount >> 24);
out.write(board.viewCount >> 16);
out.write(board.viewCount >> 8);
out.write(board.viewCount);
// long ==> byte[]
System.out.printf("%016x\n", board.createdDate);
out.write((int)(board.createdDate >> 56));
out.write((int)(board.createdDate >> 48));
out.write((int)(board.createdDate >> 40));
out.write((int)(board.createdDate >> 32));
out.write((int)(board.createdDate >> 24));
out.write((int)(board.createdDate >> 16));
out.write((int)(board.createdDate >> 8));
out.write((int)(board.createdDate));
}
out.close();
}