* Client/Server 통신 규칙 (Protocol)
* 요청 정보 보내기 작업에 참여하는 객체들 (추가)
ex) 게시글 등록하기
개방 폐쇄 원칙에 따라서 확장에 대해서는 열려있고 수정에 대해서는 닫혀 있어야 한다.
즉 기존의 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.
BoardAddLinstener 는 app35 에서는 List,
app36에서는 BoardListDao, app37 에서는 BoardNetworkDao 를 통해서
하나의 프로그램을 여러 관리자가 수정할 수 있게 했다.
* 참여하는 객체 - app 35 BoardListListener
app35.
BoardListListener
BoardListListener의 생성자를 통해 List<Board>를 전달받습니다.
데이터 액세스를 위한 BoardDao 인터페이스나 구현체를 사용하지 않고,
전달받은 List<Board>를 직접 사용하여 데이터를 출력합니다.
데이터 액세스 기능이 제한적이며, List<Board>를 통해 데이터를 관리합니다.
package bitcamp.myapp.handler;
import java.util.Iterator;
import java.util.List;
import bitcamp.myapp.vo.Board;
import bitcamp.util.BreadcrumbPrompt;
public class BoardListListener extends AbstractBoardListener {
public BoardListListener(List<Board> list) {
super(list);
}
@Override
public void service(BreadcrumbPrompt prompt) {
System.out.println("---------------------------------------");
System.out.println("번호, 제목, 작성자, 조회수, 등록일");
System.out.println("---------------------------------------");
Iterator<Board> iterator = list.iterator();
while (iterator.hasNext()) {
Board board = iterator.next();
System.out.printf("%d, %s, %s, %d, %tY-%5$tm-%5$td\n",
board.getNo(),
board.getTitle(),
board.getWriter(),
board.getViewCount(),
board.getCreatedDate());
}
}
}
* 참여하는 객체 - app36 BoardListDao
BoardListDao
BoardListDao 클래스를 사용하여 데이터 액세스 기능을 구현합니다.
BoardListDao는 파일을 통해 데이터를 저장하고 조회하는 기능을 제공합니다.
BoardListDao의 생성자를 통해 파일명을 전달받아 초기화합니다.
데이터 액세스 기능이 파일 시스템을 이용하므로, 데이터의 영구 보관 및 유지 관리가 가능합니다.
package bitcamp.myapp.dao;
import java.util.ArrayList;
import java.util.List;
import bitcamp.myapp.vo.Board;
import bitcamp.util.JsonDataHelper;
public class BoardListDao implements BoardDao {
String filename;
ArrayList<Board> list = new ArrayList<>();
public BoardListDao(String filename) {
this.filename = filename;
JsonDataHelper.loadJson(filename, list, Board.class);
}
@Override
public void insert(Board board) {
board.setNo(Board.boardNo++);
board.setCreatedDate(System.currentTimeMillis());
this.list.add(board);
JsonDataHelper.saveJson(filename, list);
}
@Override
public List<Board> list() {
return this.list;
}
@Override
public Board findBy(int no) {
for (int i = 0; i < this.list.size(); i++) {
Board m = this.list.get(i);
if (m.getNo() == no) {
return m;
}
}
return null;
}
@Override
public int update(Board board) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getNo() == board.getNo()) {
list.set(i, board);
JsonDataHelper.saveJson(filename, list);
return 1;
}
}
return 0;
}
@Override
public int delete(int no) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getNo() == no) {
list.remove(i);
JsonDataHelper.saveJson(filename, list);
return 1;
}
}
return 0;
}
}
* 참여하는 객체 - app37 BoardNetworkDao
app37:
BoardNetworkDao
BoardNetworkDao 클래스를 사용하여 데이터 액세스 기능을 구현합니다.
BoardNetworkDao는 네트워크를 통해 데이터를 주고받는 기능을 제공합니다.
BoardNetworkDao의 생성자를 통해 데이터 관련 정보를
+(dataName, DataInputStream, DataOutputStream)
전달받아 초기화합니다.
데이터 액세스 기능이 네트워크를 이용하므로, 원격 서버와 통신하여 데이터를 주고받을 수 있습니다.
따라서, app35는 단순한 리스트를 사용하여 데이터를 관리하고 출력하는 기능을 제공하며,
app36과 app37은 파일 시스템과 네트워크를 통한 데이터 액세스를 지원하는 확장된 기능을 제공합니다.
이렇게 각 애플리케이션은 BoardListListener를 사용하되,
데이터 액세스 기능에 대한 구현체만 다르게 주입하여 동작하는 차이가 있습니다.
package bitcamp.myapp.dao;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import bitcamp.myapp.vo.Board;
public class BoardNetworkDao implements BoardDao {
String dataName;
DataInputStream in;
DataOutputStream out;
public BoardNetworkDao(String dataName, DataInputStream in, DataOutputStream out) {
this.dataName = dataName;
this.in = in;
this.out = out;
}
@SuppressWarnings("unchecked")
@Override
public void insert(Board board) {
try {
// 서버에서 실행할 명령을 보낸다.
out.writeUTF(dataName + "/insert");
// 명령을 실행할 때 사용할 데이터를 보낸다.
Gson gson = new Gson();
String jsonStr = gson.toJson(board);
out.writeUTF(jsonStr);
// 서버에서 보낸 응답을 받는다.
Map<String,Object> response = gson.fromJson(in.readUTF(), Map.class);
if (!response.get("status").equals("success")) {
throw new RuntimeException((String)response.get("message"));
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
@SuppressWarnings("unchecked")
@Override
public List<Board> list() {
try {
out.writeUTF(dataName + "/list");
Gson gson = new Gson();
Map<String,Object> response = gson.fromJson(in.readUTF(), Map.class);
if (!response.get("status").equals("success")) {
throw new RuntimeException((String)response.get("message"));
}
return gson.fromJson(
(String)response.get("data"),
TypeToken.getParameterized(List.class, Board.class).getType());
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
return null;
}
@Override
public Board findBy(int no) {
return null;
}
@Override
public int update(Board board) {
return 0;
}
@Override
public int delete(int no) {
return 0;
}
}
* 참여하는 객체 - app36~37 BoardDao
app36~37
BoardDao
BoardDao는 게시판(Board) 데이터의 영속성(persistence)을 관리하기 위한 인터페이스입니다.
다른 클래스들이 게시판 데이터에 접근하고 조작하는 데 사용할 수 있는 일련의 메서드를 정의합니다.
주요 메서드는 다음과 같습니다:
insert(Board board): 새로운 게시물을 게시판에 추가하는 메서드입니다.
전달된 Board 객체를 저장소에 추가합니다.
list(): 게시판에 있는 모든 게시물을 조회하는 메서드입니다.
저장소에서 모든 게시물을 가져와 리스트로 반환합니다.
findBy(int no): 주어진 번호에 해당하는 게시물을 조회하는 메서드입니다.
저장소에서 해당 게시물을 검색하여 반환합니다.
update(Board board): 게시물을 수정하는 메서드입니다.
주어진 Board 객체에 대응하는 게시물을 찾아 내용을 업데이트합니다.
delete(int no): 주어진 번호에 해당하는 게시물을 삭제하는 메서드입니다.
저장소에서 해당 게시물을 삭제합니다.
package bitcamp.myapp.dao;
import java.util.List;
import bitcamp.myapp.vo.Board;
public interface BoardDao {
void insert(Board board);
List<Board> list();
Board findBy(int no);
int update(Board board);
int delete(int no);
}
* HashMap과 Json 문자열로 변환하여 서버에 보내는 이유
Hash 맵에 있는 내용을 toJson을 활용하여 문자열로 변환하여 서버에 보내면
Board 객체에 따로 계속해서 담을 필요가 없이 간단하게 적용시킬 수 있다.
Hash 맵은 중복된 값을 허용하지 않기 때문에 게시판을 만드는 시스템에서
적절하게 사용할 수 있는 자료구조이다.
적용 전)
적용 후)
@SuppressWarnings("unchecked")
@Override
public void insert(Board board) {
try {
Gson gson = new Gson();
// 서버에 보낼 명령과 데이터를 Map 객체에 담는다.
HashMap<String, Object> request = new HashMap<>();
request.put("command", dataName + "/intsert");
request.put("data", gson.toJson(board));
System.out.println(gson.toJson(request));
// Map 객체에 담은 정보를 Json에 문자열로 변환하여 서버에 보낸다.
out.writeUTF(new Gson().toJson(request));
// 서버에서 보낸 응답을 받는다.
Map<String, Object> response = gson.fromJson(in.readUTF(), Map.class);
if (!response.get("status").equals("success")) {
throw new RuntimeException((String) response.get("result"));
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
* 38. 데이터 전송/수신 기능을 캡슐화 하기
Client / Server 간에 네트워크 프로토콜이 변경되면
변경된 규칙에 따라 데이터를 입출력하도록 DAO 안의 모든 메서드를 변경해야한다.
프로토콜이 변경되더라도 DAO는 영향을 받지 않아야한다.
ㄴSOLID의 OCP (Open-Close_princiles) 를 만족!
* Project 간에 클래스 공유하기
app client를 복사하여
app-common 폴더를 만들어준다.
왜냐하면 app-common 에서 net 을 관리해주어야
app-client 와 app-server 가 구동되어도 작업 중에 영향이 없기 때문이다.
그후 app-common 폴더의 build.gradle과 settings.gradle 을 수정해준다.
build.gradle
settings.gradle
gradle cleanEclipse
gradle Eclipse
* 각각의 기능을 분리해주기 (Server, Common, Client)
myapp-client: 클라이언트 애플리케이션을 담당합니다.
사용자 인터페이스(UI)를 표시하고,
서버에 요청을 보내고 응답을 처리하는 역할을 합니다.
주로 GUI나 CLI와 같은 사용자 인터페이스와 관련된 코드를 포함합니다.
myapp-common: 공통으로 사용되는 코드와 라이브러리를 관리하는 모듈입니다.
다른 모듈에서 사용하는 중복 코드나 유틸리티 클래스, 공통 설정 등을 포함합니다.
주로 유틸리티 함수, 상수, 공통 설정 파일 등을 포함합니다.
myapp-server: 서버 애플리케이션을 담당합니다. 클라이언트의 요청을 처리하고,
데이터베이스나 외부 서비스와의 상호작용을 담당합니다.
주로 서버 소켓을 생성하고 클라이언트와의 통신을 처리합니다.
폴더트리)