코딩항해기

[실습/JAVA] 게시판 만들기 (MVC 패턴) 본문

problem solving/과제&실습 코딩

[실습/JAVA] 게시판 만들기 (MVC 패턴)

miniBcake 2024. 7. 18. 10:04

 

 

MVC패턴을 연습하기 위해 게시판 프로그램을 만들자.

(입력 유효성 검사 등은 수업 진행을 위해 생략됨)

 

CRUD

게시글을 작성 C

게시글 전체 목록 확인 - 번호 | 제목 | 조회수 R -selectAll

게시글 1개 내용 확인 - 제목 | 내용 | 조회수 R- selecOne

조회수++ U

게시글 내용 변경 U

게시글 제목 변경 U

게시글 삭제 D

게시글 제목으로 검색해서 확인 R-selectAll

 

게시글 : 제목 / 내용 / 조회수 / pk(번호)

 

 


 

[목차]

 

[코드]

 

Client > Controller > Model (DTO, DAO) > View 

 

+ 더 나은 코드를 위해 고민하기

 


 

[코드]

 

client

더보기
package client;

import controller.BoardController;

public class Client {
	public static void main(String[] args) {
		//어플설치
		BoardController app = new BoardController();
		//실행
		app.start();
	}
}

 

controller

더보기
package controller;

import java.util.ArrayList;

import model.BoardDAO;
import model.BoardDTO;
import view.BoardView;

public class BoardController {
	//controller는 프로그램을 실행시킬 의무가 있다.
	private BoardView view;
	private BoardDAO model;
	private int PK;
	
	public BoardController() {
		super();
		this.view = new BoardView();
		this.model = new BoardDAO();
		this.PK = 102;
	}

	public void start() {
		while(true) {
//			view.메뉴목록을 보여줘();
//			int action = View.사용자로부터 원하는 메뉴 번호를 받아옴();
			view.printMenuList();
			int action = view.inputMenuNum();
			//사용자에게 메뉴 목록을 보여주면, 
			//사용자가 원하는 메뉴를 입력하겠지?
			
			if(action == 0) {//사용자가 종료라고 하면
				//view.프로그램을 종료합니다();
				view.exit();
				break;
			}
			else if(action == 1) {//게시글 작성
				//제목, 내용  = view.사용자에게 글 작성시 필요한 제목, 내용을 알려줘();
				BoardDTO data = view.boardWrite();
				//boolean flag = model.insert(제목, 내용);
				
				//하나의 요청 하나의 객체 (늘 정확한 정보를 전달하기 위해서)
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setNum(PK++);
				boardDTO.setTitle(data.getTitle());
				boardDTO.setContent(data.getContent());
				boolean flag = model.insert(boardDTO);
				if(flag) {
					//view.성공
					view.printTrue();
				}
				else {
					//view.실패
					view.printFalse();
				}
			}
			else if(action == 2) {//전체 목록 출력
				//전체목록 = model.selectAll();
				//view.전체목록보여줘(전체목록);
				//view.전체목록보여줘(model.selectAll());
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setCondition("ALL");
				view.printBoardList(model.selectAll(boardDTO));
			}
			else if(action == 3) {//글 1개 검색 == 글 선택과 같다
				//int num = view.사용자에게 글 번호를 선택받으면();
				int num = view.inputNum();
				//글 = model.selectOne(글);
				BoardDTO boardDTO1 = new BoardDTO();
				boardDTO1.setNum(num);
				
				BoardDTO data = model.selectOne(boardDTO1);
				
				if(data == null) {
					//검색 결과가 없다면
					view.printFalse();
					continue;
				}
				
				BoardDTO boardDTO2 = new BoardDTO();
				boardDTO2.setNum(data.getNum());
				boardDTO2.setCondition("VIEW");
				//방문 즉시 조회수++
				//model.update(num);
				boolean flag = model.update(boardDTO2);
				
				if(!flag) {
					//에러발생
					view.printFalse();
					continue;
				}
				
				//view.보여줘(사용자가 입력한 번호에 해당하는 글을);
				view.printBoardData(data);
				
			}
			else if(action == 4) {//내용 변경
				//BoardDTO data = view.사용자에게 내용 변경할 글 번호와 내용 입력받기();
				BoardDTO data = view.inputNumContent(); //이걸 분리하면 inputNum코드를 재활용 할 수 있다. (현재 방식은 결합도가 높음!)
				//boolean flag = model.update(글 번호, 글 내용);
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setCondition("CONTENT");
				boardDTO.setNum(data.getNum());
				boardDTO.setContent(data.getContent());
				boolean flag = model.update(boardDTO);
				if(flag) {
					//성공
					view.printTrue();
				}
				else {
					//실패
					view.printFalse();
				}
				//selectOne을 수행하고 update를 하는 방법도 있다. 이 경우 조회수가 올라야한다!
			}
			else if(action == 5) {//제목 변경
				BoardDTO data = view.inputNumTitle();
				
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setNum(data.getNum());
				boardDTO.setTitle(data.getTitle());
				boardDTO.setCondition("TITLE");
				boolean flag = model.update(boardDTO);
				if(flag) {
					//성공
					view.printTrue();
				}
				else {
					//실패
					view.printFalse();
				}
			}
			else if(action == 6) {//글 삭제
				int num = view.inputNum();
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setNum(num);
				boolean flag = model.delete(boardDTO);
				if(flag) {
					//성공
					view.printTrue();
				}
				else {
					//실패
					view.printFalse();
				}
			}
			else if(action == 7) {//제목으로 검색
				//제목 입력받기
				String keyword = view.search();
				//해당하는 게시글들을 전부 찾기
				BoardDTO boardDTO = new BoardDTO();
				boardDTO.setTitle(keyword);
				boardDTO.setCondition("KEYWORD");
				ArrayList<BoardDTO> datas = model.selectAll(boardDTO);
				//보여주기
				view.printBoardList(datas);
			}
				
		}
	}
}

 

Model - DTO

더보기
package model;

//데이터 그 자체를 의미한다.
//게시글
//게시글 :  pk(번호) / 제목 / 내용 / 조회수
public class BoardDTO {
	private int num;
	private String title;
	private String content;
	private int cnt;
	
	//개발자에게 필요한 데이터
	private String condition;
	
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public int getCnt() {
		return cnt;
	}
	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
	public String getCondition() {
		return condition;
	}
	public void setCondition(String condition) {
		this.condition = condition;
	}
	@Override
	public String toString() {
		return "BoardDTO [num=" + num + ", title=" + title + ", content=" + content + ", cnt=" + cnt + "]";
	}
	
}

 

Model - DAO

더보기
package model;

import java.util.ArrayList;

public class BoardDAO {
	//데이터베이스 역할
	private ArrayList<BoardDTO> datas;

	public BoardDAO() {
		this.datas = new ArrayList<BoardDTO>();

		//샘플 데이터 위치
		BoardDTO data = new BoardDTO();
		data.setNum(101);
		data.setTitle("샘플");
		data.setContent("+++++");
		data.setCnt(0);
		this.datas.add(data);
	}

	//CUD
	public boolean insert(BoardDTO boardDTO) {
		BoardDTO data = new BoardDTO();
		data.setNum(boardDTO.getNum());
		data.setTitle(boardDTO.getTitle());
		data.setContent(boardDTO.getContent());
		data.setCnt(0);
		this.datas.add(data);
		return true;
	}
	public boolean update(BoardDTO boardDTO) {	
		System.out.println("      log BoardDAO update(BoardDTO) start : "+boardDTO);
		for(BoardDTO data : datas) {
			if(data.getNum() == boardDTO.getNum()) {

				if(boardDTO.getCondition().equals("VIEW")) {
					System.out.println("      log BoardDAO update(BoardDTO) view cnt : "+data.getCnt());
					data.setCnt(data.getCnt()+1);
					System.out.println("      log BoardDAO update(BoardDTO) view success");
				}
				
				else if(boardDTO.getCondition().equals("TITLE")) {
					data.setTitle(boardDTO.getTitle());
					System.out.println("      log BoardDAO update(BoardDTO) title success : "+data);
				}
				
				else if(boardDTO.getCondition().equals("CONTENT")) {
					data.setContent(boardDTO.getContent());
					System.out.println("      log BoardDAO update(BoardDTO) content success : "+data);
				}
				
				else {
					System.out.println("      log BoardDAO update(BoardDTO) Condition fail");
					break;
				}
				
				System.out.println("      log BoardDAO update(BoardDTO) end : "+data.getNum());
				return true;

			}
		}
		System.out.println("      log BoardDAO update(BoardDTO) fail");
		return false;
	}

	public boolean delete(BoardDTO boardDTO) {
		System.out.println("      log BoardDAO delete(num) start : "+boardDTO.getNum());
		for(int i=0; i<datas.size(); i++) {
			if(datas.get(i).getNum() == boardDTO.getNum()) {
				System.out.println("      log BoardDAO delete(num) find : "+datas.get(i));
				this.datas.remove(i);
				System.out.println("      log BoardDAO delete(num) success");
				return true;
			}
		}
		System.out.println("      log BoardDAO delete(num) fail");
		return false;
	}

	//R
	public ArrayList<BoardDTO> selectAll(BoardDTO boardDTO) {
		ArrayList<BoardDTO> datas = new ArrayList<>();

		if(boardDTO.getCondition().equals("ALL")) {
			datas.addAll(this.datas); //원본 데이터를 넘기면 안됨
			System.out.println("      log BoardDAO selectAll() addAll");
		}
		
		else if(boardDTO.getCondition().equals("KEYWORD")) {
			System.out.println("      log BoardDAO selectAll(keyword) start : "+boardDTO.getTitle());
			for(BoardDTO data : this.datas) {
				if(data.getTitle().contains(boardDTO.getTitle())) {
					System.out.println("      log BoardDAO selectAll(keyword) find : "+data.getTitle());
					datas.add(data);
				}
			}
		}
		
		else {
			System.out.println("      log BoardDAO selectAll() Condition errer ");
		}

		System.out.println("      log BoardDAO selectAll() end : "+ !datas.isEmpty());
		return datas;
	}

	public BoardDTO selectOne(BoardDTO boardDTO) {
		for(BoardDTO data : datas) {
			if(data.getNum() == boardDTO.getNum()) {
				return data;
			}
		}
		return null;
	}
}

 

View

더보기
package view;

import java.util.ArrayList;
import java.util.Scanner;

import model.BoardDTO;

public class BoardView {
	static Scanner sc = new Scanner(System.in);
	

	public void printBoardData(BoardDTO data) {
		//인자로 넘어오는 data가 null일 수는 없다는 스탠스
		//Controller가 올바른 값을 넘겨줄 거라는 믿음..
		System.out.println("제목 : "+data.getTitle());
		System.out.println("내용 : "+data.getContent());
		System.out.println("조회수 : "+data.getCnt());
	}
	
	public void printTrue() {
		System.out.println("성공!");
	}
	
	public void printFalse() {
		System.out.println("실패..");
		System.out.println("관리자에게 문의해주세요!");
	}
	
	public void printBoardList(ArrayList<BoardDTO> datas) {
		if(datas.isEmpty()) {
			System.out.println("보여줄 데이터 없음!");
			return;
		}
		
		System.out.println("==== 전체 목록 ====");
		System.out.println("번호 | 제목 | 조회수");
		System.out.println("-----------------");
		for(BoardDTO data:datas) {
			System.out.println(data.getNum()+" | "+data.getTitle()+" | "+data.getCnt());
		}
		System.out.println("=================");
	}
	
	public void printMenuList() {
		System.out.println("=== 메뉴 목록 ===");
		System.out.println("1. 글 작성");
		System.out.println("2. 전체출력");
		System.out.println("3. 1개검색");
		System.out.println("4. 내용변경");
		System.out.println("5. 제목변경");
		System.out.println("6. 글 삭제");
		System.out.println("7. 제목으로 검색");
		System.out.println("0. 프로그램 종료");
		System.out.println("===============");
	}
	
	public void exit() {
		System.out.println("프로그램을 종료합니다...");
	}
	///////////////////////////////////////////////
	public int inputNum() {
		System.out.print("글 번호 입력 >> ");
		int num = sc.nextInt();
		return num;
	}
	
	public BoardDTO boardWrite() {
		System.out.print("글 제목 입력 >> ");
		String title = sc.next();
		System.out.print("글 내용 입력 >> ");
		String content = sc.next();
		
		BoardDTO data = new BoardDTO();
		data.setTitle(title);
		data.setContent(content);
		System.out.println("       로그 : View boardWrite() return : "+data);
		return data;
	}
	
	public int inputMenuNum() {
		System.out.print("메뉴입력 >> ");
		int num = sc.nextInt();
		//유효성
		return num;
	}
	
	public String search() {
		System.out.print("검색어 입력 >> ");
		String keyword = sc.next();
		return keyword;
	}
	
	public BoardDTO inputNumTitle() {
		System.out.print("글 번호 입력 >> ");
		int num = sc.nextInt();
		System.out.print("변경할 제목 입력 >> ");
		String title = sc.next();
		
		BoardDTO data = new BoardDTO();
		data.setNum(num);
		data.setTitle(title);
		return data;
	}
	
	public BoardDTO inputNumContent() {
		System.out.print("글 번호 입력 >> ");
		int num = sc.nextInt();
		System.out.print("변경할 내용 입력 >> ");
		String content = sc.next();
		
		BoardDTO data = new BoardDTO();
		data.setNum(num);
		data.setContent(content);
		return data;
	}
}

 

+ 더 나은 코드를 위해 고민하기

ver1.

	public boolean update(BoardDTO boardDTO) {
		System.out.println("      log BoardDAO update(BoardDTO) start : "+boardDTO);
		for(BoardDTO data : datas) {
			if(data.getNum() == boardDTO.getNum()) {

				if(boardDTO.getCondition().equals("VIEW")) {
					System.out.println("      log BoardDAO update(BoardDTO) view cnt : "+data.getCnt());
					data.setCnt(data.getCnt()+1);
					System.out.println("      log BoardDAO update(BoardDTO) view success");
				}
				
				else if(boardDTO.getCondition().equals("TITLE")) {
					data.setTitle(boardDTO.getTitle());
					System.out.println("      log BoardDAO update(BoardDTO) title success : "+data);
				}
				
				else if(boardDTO.getCondition().equals("CONTENT")) {
					data.setContent(boardDTO.getContent());
					System.out.println("      log BoardDAO update(BoardDTO) content success : "+data);
				}
				
				else {
					System.out.println("      log BoardDAO update(BoardDTO) Condition fail");
					break;
				}
				
				System.out.println("      log BoardDAO update(BoardDTO) end : "+data.getNum());
				return true;

			}
		}
		System.out.println("      log BoardDAO update(BoardDTO) fail");
		return false;
	}

 

ver2.

	public boolean update(BoardDTO boardDTO) {
		System.out.println("      log BoardDAO update(BoardDTO) start : "+boardDTO);

		if(boardDTO.getCondition().equals("VIEW")) {
			for(BoardDTO data : datas) {
				if(data.getNum() == boardDTO.getNum()) {
					System.out.println("      log BoardDAO update(BoardDTO) view cnt : "+data.getCnt());
					data.setCnt(data.getCnt()+1);
					System.out.println("      log BoardDAO update(BoardDTO) view success");
					return true;
				}
			}
		}
		else if(boardDTO.getCondition().equals("TITLE")) {
			for(BoardDTO data : datas) {
				if(data.getNum() == boardDTO.getNum()) {
					data.setTitle(boardDTO.getTitle());
					System.out.println("      log BoardDAO update(BoardDTO) title success : "+data);
					return true;
				}
			}
		}
		else if(boardDTO.getCondition().equals("CONTENT")) {
			for(BoardDTO data : datas) {
				if(data.getNum() == boardDTO.getNum()) {
					data.setContent(boardDTO.getContent());
					System.out.println("      log BoardDAO update(BoardDTO) content success : "+data);
					return true;
				}
			}
		}
		else {
			System.out.println("      log BoardDAO update(BoardDTO) Condition fail");
		}
		System.out.println("      log BoardDAO update(BoardDTO) fail");
		return false;
	}

 

 

ver1과 ver2의 차이는 for문과 num과 일치하는 조건을 묻는 if문의 위치 차이이다.

ver1의 경우는 for문과 if문을 거친 뒤에 condition의 값을 확인하여 분기를 나누며, ver2는 분기를 나눈 뒤 for문과 if문을 거치게 된다. 

 

스스로 생각할 때

ver1의 장점은 가독성이 높다는 점이었다. 단점은 condition 분기에 해당하지 않는 else의 값이 들어오게 될때에도 for문과 if문을 거쳐야한다는 점이다. 

ver2의 장점은 condition 분기에 해당하지 않는 값이 들어오게 되면 for문과 if문을 거치지 않고 바로 false를 반환한다.

블록 단위로 되어있어 분기 별 코드를 별도로 유지보수하기 좋다는 점도 있다. 단점은 가독성이 떨어지고, 뎁스가 ver1에 비해 깊은 편이다.

 

chatGPT

스스로 생각하는 선에서는 놓치는 부분도 많고 모르는 부분도 많기 때문에 선생님께 도움을 구하기 전에 ai 시대인만큼 chatGPT에게도 물어봤다. 

더보기

두 버전의 코드를 비교한 결과, ver2의 코드가 더 나은 선택이라는 결론이 나옵니다.


1. 코드 구조와 논리
조건 분기 처리: ver2는 boardDTO.getCondition()에 따라 각 조건별로 분리된 for 루프를 사용하여 가독성이 더 좋습니다. 반면에 ver1은 조건 분기 안에 for 루프가 중첩되어 있어서 코드의 복잡성을 증가시킵니다.

 

루프 반복 횟수 감소: ver2는 각 조건별로 루프를 돌기 때문에 필요 없는 루프 반복을 줄일 수 있습니다. 예를 들어, VIEW 조건일 때만 관련된 데이터만 검색하고, 다른 조건일 때는 해당 조건에 대해서만 검색합니다. ver1은 조건과 상관없이 모든 데이터에 대해 조건을 체크하기 때문에 비효율적일 수 있습니다.


2. 유지보수
유지보수의 용이성: ver2는 각 조건에 대한 코드 블록이 명확하게 분리되어 있어서 코드 수정 및 유지보수가 더 쉽습니다. 각 조건에 대한 코드 변경 시 다른 조건에 영향을 미치지 않기 때문입니다.


3. 코드 가독성
명확한 조건 분리: ver2에서는 각 조건 (VIEW, TITLE, CONTENT)별로 분리된 if-else 블록이 있어 코드가 깔끔하고 각 조건에 대한 로직이 명확하게 구분됩니다. ver1에서는 모든 조건이 한 루프 안에 중첩되어 있어, 코드가 복잡해 보이고 각 조건에 대한 로직을 찾기가 어렵습니다.

 

간결한 조건 확인: ver2에서는 boardDTO.getCondition()에 따라 한 번의 루프만 돌면서 조건을 확인하고 바로 처리합니다. 이는 조건별로 처리 방식을 이해하기 쉽게 만듭니다. ver1에서는 모든 데이터에 대해 조건을 확인한 후, 조건에 맞는 작업을 수행하는 구조로, 불필요하게 조건 확인이 반복되고 코드가 장황해집니다.

 

디버깅 용이성: ver2의 경우 조건별로 분리된 로직 덕분에 각 조건에 대한 디버깅이 용이합니다. 예를 들어 TITLE 조건에서 문제가 발생하면 해당 블록만 집중적으로 확인하면 됩니다. ver1은 중첩된 구조 때문에 특정 조건에서 발생하는 문제를 찾기가 어렵고, 디버깅 시 많은 시간을 소비할 수 있습니다.

 

읽기 쉬운 로깅: ver2는 로깅 메시지가 각 조건별로 구분되어 있어, 로그를 통해서도 쉽게 어떤 조건에서 문제가 발생했는지 파악할 수 있습니다. ver1은 로깅 메시지가 중첩된 구조 안에 있어, 로그를 통해서 문제를 추적하기가 어렵습니다.

결론
ver2의 코드는 구조가 명확하고, 조건별로 분리된 로직 덕분에 가독성이 좋고 유지보수가 용이합니다.
또한 불필요한 루프 반복을 줄임으로써 성능 향상에도 기여할 수 있습니다.
따라서 ver2 코드가 더 나은 선택입니다.

 

ver1이 가독성이 더 좋다고 생각했는데 chatGPT는 반대의 답을 냈다. 확실히 codition 분기 후 안의 코드만 본다면 반복문과 if문이 빠져있어 가독성이 떨어지는 측면도 있는 것 같다.

 

chatGPT가 완벽하진 않으므로 선생님께 조언을 구했다.

 

선생님

두 코드를 비교할 때 java의 측면에서 볼 때는 ver1이 낫다고 하셨다.

하지만 현재 웹개발을 위해 JAVA를 배우고 있기 때문에

웹개발을 하면서 나아가는 방향인 ver2로 공부하는 것을 추천한다고 하셨다.

 

결론

웹개발에 더 어울리는 방향으로 공부하자!