[0701]
settings.gradle
rootProject.name = 'TodoApp'
include ':ScoulaCli'
project(':ScoulaCli').projectDir=new File('C:\\KB_ITs_Your_Life\\Scoula\\ScoulaLib\\scoulacli')
build.gradle
...
dependencies {
implementation project(':ScoulaCli')
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
...
BadMenuException.java
public class BadMenuException extends Exception {
public BadMenuException() {
super("잘못된 메뉴 선택"); // super() -> 부모 생서자 호출
}
public BadMenuException(String message) {
super(message);
}
}
App.java
...
public void run() {
init();
while(true) {
try {
menu.printMenu();
Command command = menu.getSelect(); // 예외 -> BadMenuException로 대체해서 예외 던지기
command.execute();
} catch (Exception e) {
e.printStackTrace();
System.out.println("에러: " + e.getMessage());
}
}
}
...
Menu.java
...
public Command getSelect() throws Exception {
try {
int selectNo = Input.getInt("선택> ");
return menus.get(selectNo - 1).getCommand();
} catch (Exception e) {
throw new BadMenuException(); // BadMenuException로 대체해서 예외 던지기
}
}
}
...
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", null)); // Command 객체 아직 준비 x -> Null
menu.add(new MenuItem("상세", null));
menu.add(new MenuItem("추가", null));
menu.add(new MenuItem("수정", null));
menu.add(new MenuItem("삭제", null));
// 현재는 6.종료만 정상 실행
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
Todo.java
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 얘네를 다 받아줌
@Data // getter, setter 다 포함 ...
public class Todo implements Cloneable{ // 복제(clone)을 허용하는 인터페이스
private static int gid = 1; // Todo ID 발급을 위한 스태틱 변수 (유일한 정수값), 일종의 ID 발급기^_^
// 멤버 정의
private int id;
private String title;
private String description;
private boolean done;
private Date date;
@Builder // 이 생성자에 준해서 Builder를 운영
// 호출 할 수 있는 설정: title(), description(), done() / id, date에 대해서는 Building 못함
public Todo (String title, String description, boolean done) {
this(gid++, title, description, done, new Date());
}
}
Data Access Object
- 데이터에 대한 CRUD 처리 담당
- 데이터 소스(배열, List, DB 등)에 대한 처리를 상위 계층(비지니스 계층)으로 부터 분리
이 데이터로 뭘 할지는 몰라~!
오직 데이터 조작만 담당한다~~!!
비지니스 로직 생각X, 데이터만 바라봐
TodoDao.java
public class TodoDao {
// 싱글톤 패턴
private static TodoDao instance = new TodoDao();
public static TodoDao getInstance() {
return instance;
}
private List<Todo> list; // 리스트를 사용해서 운영
private TodoDao() {
list = new ArrayList<>();
for(int i = 0; i < 10; i++) { // 임시 테스트 구성
Todo todo = Todo.builder()
.title("Todo " + i)
.description("Description " + i)
.done(false)
.build();
list.add(todo);
}
}
// 리스트에 대한 CRUD
// C : 추가
// R : 목록read, 하나read
// U : 교체
// D : 리스트에서 제거
public List<Todo> getList() {
return list;
}
// 단인 element 읽기
public Todo getTodo(int id) {
for(Todo todo: list) {
if(todo.getId() == id) {
return todo;
}
}
return null;
}
// 추가
public void add(Todo todo) {
list.add(todo);
}
// 업데이트
public void update(Todo todo) {
for(int i = 0; i < list.size(); i++) {
if(todo.getId() == list.get(i).getId()) {
list.set(i, todo);
}
}
}
// 삭제
public void delete (int id) {
for(int i = 0; i < list.size(); i++) {
if(list.get(i).getId() == id) {
list.remove(i);
return;
}
}
}
}
[0702]
포맷팅
- 출력
- printf(...)
- 문자열 리턴
- String line = "%2d] %s".formatted(todo.getId), todo.getTItle());
PrintTodoCommand.java
public class PrintTodoCommand implements Command {
TodoDao dao = TodoDao.getInstance(); // Dao - 싱글톤으로 운영
@Override
public void execute() {
for (Todo todo: dao.getList()) {
String line = "%2d] %s".formatted(todo.getId(), todo.getTitle());
System.out.println(line);
}
System.out.println();
}
}
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", new PrintTodoCommand()));
menu.add(new MenuItem("상세", null));
menu.add(new MenuItem("추가", null));
menu.add(new MenuItem("수정", null));
menu.add(new MenuItem("삭제", null));
// 현재는 6.종료만 정상 실행
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
SimpleDateFormat
특정 문자열 포맷으로 얻기 (java.text.SimpleDateFormat 클래스)
ex) 2024-06-27 15:27:41
Todo.java
public class Todo implements Cloneable{ // 복제(clone)을 허용하는 인터페이스
private static int gid = 1; // Todo ID 발급을 위한 스태틱 변수 (유일한 정수값), 일종의 ID 발급기^_^
// 멤버 정의
private int id;
private String title;
private String description;
private boolean done;
private Date date;
@Builder // 생성자 레벨에 붙인 Builder -> 이 생성자에 준해서 Builder를 운영
// 호출 할 수 있는 설정: title(), description(), done() / id, date에 대해서는 Building 못함
public Todo (String title, String description, boolean done) {
this(gid++, title, description, done, new Date());
}
@Override
public Object clone() { // 부모에서는 protect 였던 것을 public으로 변경
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
// 메서드 추가
// 문자열로 날짜를 리턴
public String getRegDate() {
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 패턴 문자열
return sdf.format(date);
}
}
DetailTodoCommand.java
public class DetailTodoCommand implements Command {
TodoDao dao = TodoDao.getInstance(); // Dao
@Override
public void execute() {
int id = Input.getInt("Todo Id: "); // 에러1) id 입력 받을 때 - 숫자가 아닌 것을 입력했을 때
Todo todo = dao.getTodo(id); // 에러2) 존재하지 않는 id를 입력 했을 때 - NullPointException 발생
System.out.println("[Todo 상세보기]-----------------------");
System.out.println("ID : " + todo.getId());
System.out.println("제목 : " + todo.getTitle());
System.out.println("설명 : " + todo.getDescription());
System.out.println("완료여부 : " + todo.isDone());
System.out.println("등록일 : " + todo.getRegDate());
System.out.println("------------------------------------");
System.out.println();
}
}
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", new PrintTodoCommand()));
menu.add(new MenuItem("상세", new DetailTodoCommand()));
menu.add(new MenuItem("추가", null));
menu.add(new MenuItem("수정", null));
menu.add(new MenuItem("삭제", null));
// 현재는 6.종료만 정상 실행
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
DeleteTodoCommand.java
// 삭제
public class DeleteTodoCommand implements Command {
TodoDao dao = TodoDao.getInstance();
@Override
public void execute() {
int id = Input.getInt("삭제할 Todo Id: ");
dao.delete(id);
System.out.println();
}
}
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", new PrintTodoCommand()));
menu.add(new MenuItem("상세", new DetailTodoCommand()));
menu.add(new MenuItem("추가", null));
menu.add(new MenuItem("수정", null));
menu.add(new MenuItem("삭제", new DeleteTodoCommand()));
// 현재는 6.종료만 정상 실행
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
[0703]
Input.java
package org.scoula.lib.cli.ui;
public class Input {
static Scanner scanner = new Scanner(System.in);
public static int getInt(String title) {
System.out.print(title);
return Integer.parseInt(scanner.nextLine());
}
// String 추가
public static String getLine(String title) {
System.out.print(title);
return scanner.nextLine();
}
}
AddTodoCommand.java
public class AddTodoCommand implements Command {
TodoDao dao = TodoDao.getInstance();
@Override
public void execute() {
System.out.println("[새 Todo 추가]-----------------------");
String title = Input.getLine("제목: ");
String description = Input.getLine("설명: ");
System.out.println("------------------------------------");
Todo todo = Todo.builder()
.title(title)
.description(description)
.done(false)
.build();
dao.add(todo);
System.out.println();
}
}
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", new PrintTodoCommand()));
menu.add(new MenuItem("상세", new DetailTodoCommand()));
menu.add(new MenuItem("추가", new AddTodoCommand()));
menu.add(new MenuItem("수정", null));
menu.add(new MenuItem("삭제", new DeleteTodoCommand()));
// 6. 프로그램 종료
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
CLI에서 가장 힘든 작업~~ 수정~~
CLI 특성상 기존 데이터 수정 어려움
Input.java
package org.scoula.lib.cli.ui;
public class Input {
static Scanner scanner = new Scanner(System.in);
public static int getInt(String title) {
System.out.print(title);
return Integer.parseInt(scanner.nextLine());
}
// getLine 수정
public static String getLine(String title, String defaultValue) {
// 이름(김길동):
System.out.printf("%s(%s): ", title, defaultValue);
String answer = scanner.nextLine();
// 그냥 엔터 쳤으면 defaultValue 리턴, 입력값 있으면 answer 리턴
return answer.isEmpty() ? defaultValue : answer;
}
// boolean 입력 (완료 여부)
// 타이틀만 주었을 경우 - true(Default)
public static boolean confirm(String title) {
return confirm(title, true); // 아래 confirm 메서드
}
// false
public static boolean confirm(String title, boolean defaultValue) {
String yesNo = defaultValue ? "(Y/n)" : "(y/N)";
System.out.printf("%s %s: ", title, yesNo);
String answer = scanner.nextLine();
if (answer.isEmpty())
return defaultValue;
return answer.equalsIgnoreCase("y"); // IgnoreCase: 대소문자 구분 없이 비교
}
}
UpdateTodoCommand.java
public class UpdateTodoCommand implements Command {
TodoDao dao = TodoDao.getInstance();
@Override
public void execute() {
int id = Input.getInt("수정할 Id: ");
Todo todo = dao.getTodo(id);
System.out.println("[Todo 수정하기]-----------------------");
System.out.println("ID : " + todo.getId());
String title = Input.getLine("제목", todo.getTitle());
String description = Input.getLine("설명", todo.getDescription());
Boolean done = Input.confirm("완료여부", todo.isDone());
System.out.println("------------------------------------");
System.out.println();
// 인스턴스 복제 : clone() 메서드 in Object
// Cloneable 인터페이스가 붙어야 clone 메서드 사용 가능
Todo updateTodo = (Todo)todo.clone();
updateTodo.setTitle(title);
updateTodo.setDescription(description);
updateTodo.setDone(done);
dao.update(updateTodo);
}
}
Todo.java
@Override
// 부모에서는 protect 였던 것을 public으로 변경
// return은 Object
// 실제 사용시에는 down casting 해야 함
// 반드시 Cloneable 인스턴스를 구현한 객체만 clone 호출 가능
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
TodoApp.java
public class TodoApp extends App {
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 번호를 입력해도 에러는 나지만 프로그램 종료는 X -> 예외처리 했기 때문
menu.add(new MenuItem("목록", new PrintTodoCommand()));
menu.add(new MenuItem("상세", new DetailTodoCommand()));
menu.add(new MenuItem("추가", new AddTodoCommand()));
menu.add(new MenuItem("수정", new UpdateTodoCommand()));
menu.add(new MenuItem("삭제", new DeleteTodoCommand()));
// 6. 프로그램 종료
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
[0704]
이미 구현체 有 ⇨ 그 구현체를 기반으로 자동으로 인터페이스 뽑아내기~!
1) 기존 TodoDao 이름 변경 ➞ TodoListDao
2) 인터페이스 추출
Refactor - Extracl/Introduce - Interface
인터페이스 이름: TodoDao
package org.scoula.todo.dao;
import org.scoula.todo.domain.Todo;
import java.util.List;
// TodoDao 인터페이스 자동 생성 완
public interface TodoDao {
List<Todo> getList();
// 단일 element 읽기
Todo getTodo(int id);
// 추가
void add(Todo todo);
// 업데이트
void update(Todo todo);
// 삭제
void delete(int id);
}
TodoListDao에 구현 파트가 자동으로 추가
메소드 앞에 오버라이드 어노테이션이 자동으로 붙음
TodoDao dao = TodoListDao.getInstance();
∴ Dao가 다른 걸로 교체되더라도 수정사항은 TodoListDao 뿐~!
- TodoService
- Todo와 관련된 Command들을 캡슐화
- 메서드 참조를 이용해 MenuItem에 설정
TodoService.java
// 캡슐화
public class TodoService {
TodoDao dao = TodoListDao.getInstance();
// Command들이 다 메서드로 들어옴
public void printTodoList() {
for (Todo todo: dao.getList()) {
String line = "%2d] %s".formatted(todo.getId(), todo.getTitle());
System.out.println(line);
}
System.out.println();
}
public void detailTodo() {
int id = Input.getInt("Todo Id: ");
Todo todo = dao.getTodo(id);
System.out.println("[Todo 상세보기]-----------------------");
System.out.println("ID : " + todo.getId());
System.out.println("제목 : " + todo.getTitle());
System.out.println("설명 : " + todo.getDescription());
System.out.println("완료여부 : " + todo.isDone());
System.out.println("등록일 : " + todo.getRegDate());
System.out.println("------------------------------------");
System.out.println();
}
public void addTodo() {
System.out.println("[새 Todo 추가]-----------------------");
String title = Input.getLine("제목: ");
String description = Input.getLine("설명: ");
System.out.println("------------------------------------");
Todo todo = Todo.builder()
.title(title)
.description(description)
.done(false)
.build();
dao.add(todo);
System.out.println();
}
public void updateTodo() {
int id = Input.getInt("수정할 Id: ");
Todo todo = dao.getTodo(id);
System.out.println("[Todo 수정하기]-----------------------");
System.out.println("ID : " + todo.getId());
String title = Input.getLine("제목", todo.getTitle());
String description = Input.getLine("설명", todo.getDescription());
Boolean done = Input.confirm("완료여부", todo.isDone());
System.out.println("------------------------------------");
System.out.println();
// 인스턴스 복제 : clone() 메서드 in Object
// Cloneable 인터페이스가 붙어야 clone 메서드 사용 가능
Todo updateTodo = (Todo)todo.clone();
updateTodo.setTitle(title);
updateTodo.setDescription(description);
updateTodo.setDone(done);
dao.update(updateTodo);
}
public void deleteTodo() {
int id = Input.getInt("삭제할 Todo Id: ");
dao.delete(id);
System.out.println();
}
}
(기존: TodoApp_v1.java)
TodoApp.java
public class TodoApp extends App {
TodoService service = new TodoService();
@Override
public void createMenu(Menu menu) {
super.createMenu(menu);
// 메서드 참조로 대체 (service 객체 인스텐스의 메서드 사용~!)
menu.add(new MenuItem("목록", service::printTodoList));
menu.add(new MenuItem("상세", service::detailTodo));
menu.add(new MenuItem("추가", service::addTodo));
menu.add(new MenuItem("수정", service::updateTodo));
menu.add(new MenuItem("삭제", service::deleteTodo));
// 6. 프로그램 종료
}
public static void main(String[] args) {
App app = new TodoApp();
app.run();
}
}
Command: Functional Interface
➞ 람다식, 메서드 참조로 대체 가능
인스턴스 복제
- clone() 메서드 (in Object)
- Cloneable 인터페이스를 구현한 객체만 호출 가능
- protect로 정의 되어 있음 ⇨ overiding 재정의 해야 함
- return은 Object
- 실제 사용 시 down casting