mini-pms 33b
Observer 객체를 통하여 파일 다루기
- ApplicationContextListener 인터페이스 변경
// 애플리케이션의 상태가 변경되었을 때
// 호출할 메서드 규칙을 정의한다.
// 즉 애플리케이션 상태 변경에 대해 보고를 받을 "Observer" 규칙을 정의한다.
// 보통 옵저버를 "리스너(listener)/구독자(subscriber)"라 부른다.
//
public interface ApplicationContextListener {
// 발행자(애플리케이션)가 애플리케이션 시작을 알리기 위해 호출하는 메서드
// - 발행자와 옵저버 간에 데이터를 공유하기 위해 맵 파라미터 추가
// - 호출자가 옵저버의 실행 결과를 받을 수 있도록 파라미터로 맵 객체를 전달할 것이다.
// - 리턴 값으로 결과를 전달하지 않고 파라미터로 넘어온 저장소에 보관하는 방법을 사용한다.
// - 왜 이런 방식을 사용하는가?
// - 파라미터 방식은 메서드에게 작업에 필요한 정보를 전달할 수 있기 때문이다.
// - Map을 사용하여 파라미터로 전달하는 방식은 데이터의 In/Out이 가능하다.
void contextInitialized(Map<String,Object> context);
// 발행자(애플리케이션)가 애플리케이션 종료를 알리기 위해 호출하는 메서드
void contextDestroyed(Map<String,Object> context);
}
- AppInitListener 클래스 변경
public class AppInitListener implements ApplicationContextListener {
@Override
// 변경된 규칙에 따라 파라미터 추가
public void contextInitialized(Map<String,Object> context) {
System.out.println("프로젝트 관리 시스템(PMS)에 오신 걸 환영합니다!");
}
@Override
// 변경된 규칙에 따라 파라미터 추가
public void contextDestroyed(Map<String,Object> context) {
System.out.println("프로젝트 관리 시스템(PMS)을 종료합니다!");
}
}
- App 변경
public class App {
Map<String,Object> context = new Hashtable<>();
List<ApplicationContextListener> listeners = new ArrayList<>();
public void addApplicationContextListener(ApplicationContextListener listener) {
listeners.add(listener);
}
public void removeApplicationContextListener(ApplicationContextListener listener) {
listeners.remove(listener);
}
// service() 실행 전에 옵저버에게 통지한다.
private void notifyApplicationContextListenerOnServiceStarted() {
for (ApplicationContextListener listener : listeners) {
// 곧 서비스를 시작할테니 준비하라고,
// 서비스 시작에 관심있는 각 옵저버에게 통지한다.
// => 옵저버에게 맵 객체를 넘겨준다.
// => 옵저버는 작업 결과를 파라미터로 넘겨준 맵 객체에 담아 줄 것이다.
listener.contextInitialized(context);
}
}
// service() 실행 후에 옵저버에게 통지한다.
private void notifyApplicationContextListenerOnServiceStopped() {
for (ApplicationContextListener listener : listeners) {
// 서비스가 종료되었으니 마무리 작업하라고,
// 마무리 작업에 관심있는 각 옵저버에게 통지한다.
// => 옵저버에게 맵 객체를 넘겨준다.
// => 옵저버는 작업 결과를 파라미터로 넘겨준 맵 객체에 담아 줄 것이다.
listener.contextDestroyed(context);
}
}
public static void main(String[] args) throws Exception {
App app = new App();
app.addApplicationContextListener(new AppInitListener());
app.service();
}
@SuppressWarnings("unchecked")
public void service() throws Exception {
notifyApplicationContextListenerOnServiceStarted();
List<Board> boardList = (List<Board>) context.get("boardList");
List<Member> memberList = (List<Member>) context.get("memberList");
List<Project> projectList = (List<Project>) context.get("projectList");
List<Task> taskList = (List<Task>) context.get("taskList");
Map<String,Command> commandMap = new HashMap<>();
commandMap.put("/board/add", new BoardAddCommand(boardList));
commandMap.put("/board/list", new BoardListCommand(boardList));
commandMap.put("/board/detail", new BoardDetailCommand(boardList));
commandMap.put("/board/update", new BoardUpdateCommand(boardList));
commandMap.put("/board/delete", new BoardDeleteCommand(boardList));
MemberListCommand memberListCommand = new MemberListCommand(memberList);
commandMap.put("/member/add", new MemberAddCommand(memberList));
commandMap.put("/member/list", memberListCommand);
commandMap.put("/member/detail", new MemberDetailCommand(memberList));
commandMap.put("/member/update", new MemberUpdateCommand(memberList));
commandMap.put("/member/delete", new MemberDeleteCommand(memberList));
commandMap.put("/project/add", new ProjectAddCommand(projectList, memberListCommand));
commandMap.put("/project/list", new ProjectListCommand(projectList));
commandMap.put("/project/detail", new ProjectDetailCommand(projectList));
commandMap.put("/project/update", new ProjectUpdateCommand(projectList, memberListCommand));
commandMap.put("/project/delete", new ProjectDeleteCommand(projectList));
commandMap.put("/task/add", new TaskAddCommand(taskList, memberListCommand));
commandMap.put("/task/list", new TaskListCommand(taskList));
commandMap.put("/task/detail", new TaskDetailCommand(taskList));
commandMap.put("/task/update", new TaskUpdateCommand(taskList, memberListCommand));
commandMap.put("/task/delete", new TaskDeleteCommand(taskList));
commandMap.put("/hello", new HelloCommand());
Deque<String> commandStack = new ArrayDeque<>();
Queue<String> commandQueue = new LinkedList<>();
loop:
while (true) {
String inputStr = Prompt.inputString("명령> ");
if (inputStr.length() == 0) {
continue;
}
commandStack.push(inputStr);
commandQueue.offer(inputStr);
switch (inputStr) {
case "history": printCommandHistory(commandStack.iterator()); break;
case "history2": printCommandHistory(commandQueue.iterator()); break;
case "quit":
case "exit":
System.out.println("안녕!");
break loop;
default:
Command command = commandMap.get(inputStr);
if (command != null) {
try {
command.execute();
} catch (Exception e) {
System.out.println("--------------------------------------------------------------");
System.out.printf("명령어 실행 중 오류 발생: %s\n", e);
System.out.println("--------------------------------------------------------------");
}
} else {
System.out.println("실행할 수 없는 명령입니다.");
}
}
System.out.println();
}
Prompt.close();
notifyApplicationContextListenerOnServiceStopped();
}
.
.
.
}
02) 파일에서 데이터를 로딩하고 저장하는 기능을 옵저버로 옮긴다.
- DataHandlerListener 클래스 생성
// 게시물, 회원, 프로젝트, 작업 데이터를 파일에서 로딩하고 파일로 저장하는 일을 한다. public class DataHandlerListener implements ApplicationContextListener { List<Board> boardList = new ArrayList<>(); File boardFile = new File("./board.json"); // 게시글을 저장할 파일 정보 List<Member> memberList = new LinkedList<>(); File memberFile = new File("./member.json"); // 회원을 저장할 파일 정보 List<Project> projectList = new LinkedList<>(); File projectFile = new File("./project.json"); // 프로젝트를 저장할 파일 정보 List<Task> taskList = new ArrayList<>(); File taskFile = new File("./task.json"); // 작업을 저장할 파일 정보 @Override public void contextInitialized(Map<String,Object> context) { // 애플리케이션의 서비스가 시작되면 먼저 파일에서 데이터를 로딩한다. // 파일에서 데이터 로딩 loadData(boardList, boardFile, Board[].class); loadData(memberList, memberFile, Member[].class); loadData(projectList, projectFile, Project[].class); loadData(taskList, taskFile, Task[].class); // 옵저버가 파일에서 데이터(게시글,회원,프로젝트,작업)를 읽어 // List 컬렉션에 저장한 다음, // 발행자(App 객체)가 사용할 수 있도록 맵 객체에 담아서 공유한다. context.put("boardList", boardList); context.put("memberList", memberList); context.put("projectList", projectList); context.put("taskList", taskList); } @Override public void contextDestroyed(Map<String,Object> context) { // 애플리케이션 서비스가 종료되면 컬렉션에 보관된 객체를 파일에 저장한다. // 데이터를 파일에 저장 saveData(boardList, boardFile); saveData(memberList, memberFile); saveData(projectList, projectFile); saveData(taskList, taskFile); } private <T> void loadData( Collection<T> list, // 객체를 담을 컬렉션 File file, // JSON 문자열이 저장된 파일 Class<T[]> clazz // JSON 문자열을 어떤 타입의 배열로 만들 것인지 알려주는 클래스 정보 ) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(file)); list.addAll(Arrays.asList(new Gson().fromJson(in, clazz))); System.out.printf("'%s' 파일에서 총 %d 개의 객체를 로딩했습니다.\n", file.getName(), list.size()); } catch (Exception e) { System.out.printf("'%s' 파일 읽기 중 오류 발생! - %s\n", file.getName(), e.getMessage()); } finally { try { in.close(); } catch (Exception e) { } } } private void saveData(Collection<?> list, File file) { BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(file)); Gson gson = new Gson(); String jsonStr = gson.toJson(list); out.write(jsonStr); out.flush(); System.out.printf("총 %d 개의 객체를 '%s' 파일에 저장했습니다.\n", list.size(), file.getName()); } catch (IOException e) { System.out.printf("객체를 '%s' 파일에 쓰는 중 오류 발생! - %s\n", file.getName(), e.getMessage()); } finally { try { out.close(); } catch (IOException e) { } } } } - App에 DataHandlerListener 옵저버를 등록한다.
public class App { . . . public static void main(String[] args) throws Exception { App app = new App(); app.addApplicationContextListener(new AppInitListener()); // 옵저버 등록 app.addApplicationContextListener(new DataHandlerListener()); app.service(); } . . . }