풀스택 개발자 과정 23일차

너구·2026년 6월 9일

풀스택 성장과정

목록 보기
23/79

TodoList 만들기

오늘은 todolist를 만드는 시간을 가져보았다.

TodoList

import java.util.ArrayList;
import java.util.List;

class TodoList {

    // 할 일 목록
    private List<String> todos = new ArrayList<>();

    public TodoList() {
        // 샘플 추가
        addTodo("밥 먹기");
        addTodo("여행하기");
        addTodo("운동하기");
    }

    // 모든 할 일 가져오기
    public List<String> getAllTodos() {
        return todos;
    }

    // 번호에 맞는 할 일 가져오기
    public String getTodoByNumber(int number) {
        int index = number -1;
        if (index < 0 || index >= todos.size()) {
            throw new IllegalStateException("할 일을 찾을 수 없습니다");
        }
        return todos.get(index);
    }

    // 할 일 추가하기
    public void addTodo(String todo) {

        if (todo.trim().length() < 1) {
            throw new IllegalStateException("할 일을 입력하세요");
        }

        todos.add(todo);
    }

    // 할 일 제거하기
    public void removeTodo(int number) {
        int index = number -1;
        if (index < 0 || index >= todos.size()) {
            throw new IllegalStateException("할일을 찾을 수 없습니다");
        }
        todos.remove(index);
    }

    // 할 일 완료하기
    public void passTodo(int number) {
        int index = number -1;

        if (index < 0 || index >= todos.size()) {
            throw new IllegalStateException("할 일을 찾을 수 없습니다");
        }

        if ((todos.get(index).startsWith("[완료]"))) {
            throw new IllegalStateException("이미 완료된 항목입니다");
        }

        todos.set(index, "[완료] " + todos.get(index));
    }
}

할 일 목록을 관리하는 TodoList 클래스를 구현했다.
이 클래스는 실제 데이터가 저장되는 공간 역할을 하며 할 일 추가, 조회, 삭제, 완료 처리와 같은 핵심 기능을 담당한다.

먼저 ArrayList를 사용해 할 일 목록을 정리했다.
ArrayList를 선택한 이유는 입력한 순서대로 데이터가 저장되며 인덱스를 통해 원하는 항목에 쉽게 접근할 수 있기 때문이다.

그 다음 객체가 생성될 때 바로 테스트할 수 있도록 생성자에서 샘플 데이터를 추가했다.
프로그램을 실행하면 기본적으로 세 개의 할 일이 등록된 상태에서 시작된다.

그 다음 getAllTodos 메서드를 통해 저장된 모든 할 일 목록을 반환한다.
Controller는 이 메서드를 호출해 데이터를 가져오고 View는 전달받은 목록을 화면에 출력한다.

그 다음 사용자가 입력한 번호를 실제 리스트의 인덱스로 변환한 뒤 해당 할 일을 반환한다.
리스트는 0번 인덱스부터 시작하지만 사용자에게는 1번부터 보여주기 때문에 1을 빼서 변환했다.
또한 존재하지 않는 번호를 입력했을 경우 예외를 발생시켜 잘못된 접근을 방지했다.

addTodo는 사용자가 입력한 할 일을 목록에 추가하는 기능이다.
추가하기 전에 입력값 검증도 진행했다.
trim()을 사용해 앞 뒤 공백을 제거한 뒤 길이를 검사한다.
공백만 입력하거나 빈 문자열을 입력한 경우 예외를 발생시켜 올바른 데이터만 저장되도록 하였다.
검ㅈ으을 통과하면 add를 통해 리스트에 추가된다.

그 다음 removeTodo로 삭제 기능을 만들었다.
삭제 역시 사용자가 입력한 번호를 인덱스로 변환한 후 진행한다.
존재하지 않는 번호를 입력하면 예외를 발생시켜 잘못된 삭제를 막는다.
이 과정을 통해 사용자는 번호만 입력해서 원하는 할 일을 제거할 수 있다.

이번 프로젝트에서 가장 흥미로웠던 기능은 완료처리 기능이다.
passTodo로 구현하였고 사용자가 완료한 항목은 삭제하지 않고 앞에 [완료] 문구를 붙여 상태를 표시하도록 구현했다.
예를 들어 운동하기는 완료 처리 이후 [완료] 운동하기로 변경된다.
구현은 set() 메서드를 활용하였다.
이미 완료된 항목에 다시 완료 명령을 입력할 수도 있기 때문에 추가 검증도 구현했다.
startWith()를 사용해 문자열이 [완료]로 시작하는지 검사한다.
이미 완료된 상태라면 예외를 발생시켜 중복 완료를 방지했다.

View

import java.util.List;

public class View {
    
    public View() {}

        public void showList(List<String> todos) {

            System.out.println();
            System.out.println("- 할 일 목록");
            if (todos.size() < 1) {
                System.out.println("할 일을 추가해보세요");
            } else {
                for (int i = 0; i <todos.size(); i++) {
                    System.out.println((i + 1) + ". " + todos.get(i));
                }
            }

            System.out.println("추가: add 할일|완료: pass 번호|삭제: drop 번호|종료: end");
        }
}

이번에는 사용자에게 화면을 보여주는 역할을 담당하는 View 클래스를 구현했다.
MVC 구조에서 View는 데이터를 직접 처리하지 않고 Controller로부터 전달받은 데이터를 화면에 출력하는 역할만 수행한다.

View 클래스의 핵심 기능은 showList() 메서드이다.
이 메서드는 Controller로부터 전달받은 할 일 목록을 화면에 출력한다.
먼저 제목을 출력해 현재 화면이 할 일 목록 화면임을 사용자에게 알려준다.

할 일이 하나도 없는 경우에는 빈 화면 대신 안내 문구를 출력하도록 구현하였다.
사용자는 현재 등록된 할 일이 없다는 사실을 쉽게 확인할 수 있고 무엇을 해야 하는지도 바로 알 수 있다.

할 일이 존재하는 경우에는 반복문을 사용해 목록 전체를 출력한다.
리스트의 인덱스는 0부터 시작하지만 사용자 입장에서는 1번부터 보는 것이 자연스럽기 때문에 i + 1을 사용했다.
이 번호는 이후 삭제나 완료 기능을 사용할 때 기준 번호로 활용된다.

목록 출력이 끝난 후에는 사용 가능한 명령어를 함께 출력한다.
사용자는 별도의 설명서를 보지 않아도 현재 사용할 수 있는 기능을 바로 확인할 수 있다.

Controller

import java.util.List;

public class Controller {

    private TodoList todoList;
    private View view;

    // 의존성 주입
    public Controller(TodoList todoList, View view) {
        this.todoList = todoList;
        this.view = view;
    }

    // 할 일 목록 처리
    public void listTodos() {
        List<String> todos = todoList.getAllTodos();
        view.showList(todos);
    }

    // 할 일 추가 처리
    public void addTodo(String todo) {
        todoList.addTodo(todo);
        List<String> todos = todoList.getAllTodos();
        view.showList(todos);
    }

    // 할 일 삭제 처리
    public void dropTodo(int number) {
        todoList.removeTodo(number);
        List<String> todos = todoList.getAllTodos();
        view.showList(todos);
    }

    // 할 일 완료 처리
    public void passTodo(int number) {
        todoList.passTodo(number);
        List<String> todos = todoList.getAllTodos();
        view.showList(todos);
    }
    
}

이번에는 Controller 역할을 담당하는 클래스이다.
Controller는 사용자 요청을 받아 실제 데이터를 처리하고 처리 결과를 View에게 전달하는 중간 관리자 역할을 수행한다.
즉 사용자의 명령가 실제 데이터 처리 로직을 연결해주는 역할을 담당한다.

Controller는 TodoList와 View 객체를 사용해야 한다.
생성자를 통해 두 객체를 전달받아 의존성 주입을 시킨다.
Controller는 직접 TodoList나 View를 생성하지 않고 외부에서 생성된 객체를 전달받아 사용한다.

목록 조회 기능은 가장 단순한 흐름을 가진다.
먼저 TodoList로부터 전체 할 일 목록을 가져온다.
그리고 가져온 데이터를 View에게 전달한다.
결과적으로 사용자는 현재 저장된 모든 할 일을 화면에서 확인할 수 있다.

사용자가 새로운 할 일을 입력하면 TodoList에 저장한다.
저장이 완료되면 최신 목록을 다시 가져온다.
마지막으로 View를 통해 갱신된 목록을 출력한다.
사용자는 추가된 결과를 즉시 확인할 수 있다.

삭제 기능도 동일한 흐름으로 동작한다.
먼저 사용자가 선택한 번호의 할 일을 삭제한다.
삭제가 완료되면 변경된 목록을 다시 가져와 출력한다.
이를 통해 사용자는 삭제 결과를 바로 확인할 수 있다.

완료 기능 역시 같은 구조를 사용한다.
TodoList에서 완료 처리를 수행한다.
그러면 선택한 항목 앞에 [완료]가 붙는다.
변경 후 최신 목록을 다시 출력해 사용자에게 결과를 보여준다.

이번 Controller 클래스는 데이터를 직접 저장하지도 않고 화면을 직접 출력하지도 않는다.
대신 TodoList와 View 객체를 연결하는 역할만 수행한다.
Controller는 사용자의 요청을 받아 적절한 TodoList 메서드를 호출하고 처리 결과를 View에게 전달한다.

Command Handler

import java.util.Scanner;

public class CommandHandler {
    
    private Controller controller;
    private Scanner scanner;

    public CommandHandler(Controller controller, Scanner scanner) {
        this.controller = controller;
        this.scanner = scanner;
    }

    public void run() {

        controller.listTodos();

        while (true) {
            System.out.print("todo> ");
            String userInput = scanner.nextLine().trim();
            String[] parseInput = userInput.split(" ", 2);
            String command = parseInput[0];
            String arg = parseInput.length > 1 ? parseInput[1] : "";

            try {
                switch (command) {
                    case "list" :
                        controller.listTodos();
                        break;
                    case "add" :
                        controller.addTodo(arg);
                        break;
                    case "pass" :
                        controller.passTodo(Integer.parseInt(arg));
                        break;
                    case "drop" :
                        controller.dropTodo(Integer.parseInt(arg));
                        break;
                    case "end" :
                        System.out.println("앱을 종료합니다");
                        return;
                        default:
                            System.out.println("잘못된 명령어");
                }
            } catch (Exception e) {
                System.out.println("오류: " + e.getMessage());
            }
        }
    }
}

이번에는 사용자가 입력한 명령어를 분석하고 적절한 Controller 메서드와 연결해주는 CommandHandler 클래스를 구현했다.
이 클래스는 TodoList 프로젝트에서 사용자와 프로그램 사이를 연결하는 역할을 담당한다.
즉 사용자가 입력한 명령어를 해석해서 어떤 기능을 실행할지 결정하는 객체이다.

CommandHandler는 Controller와 Scanner를 사용한다.
Controller는 실제 기능을 실행하기 위해 필요하고 Scanner는 사용자의 입력을 받기 위해 사용된다.
생성자를 통해 두 객체를 전달받는다.

run() 메서드가 시작되면 가장 먼저 현재 할 일 목록을 화면에 출력한다.
프로그램을 실행하자마자 사용자는 등록된 할 일을 확인할 수 있다.

프로그램이 종료될 때까지 무한 반복을 통해 사용자 입력을 받는다.
입력창은 todo> 로 출력된다.

userInput으로 입력된 문자열을 저장한다.
입력값을 공백 기준으로 나누고 명령어와 인자를 각각 분리한다.
이 과정을 통해 사용자가 입력한 내용을 프로그램이 이해할 수 있는 형태로 변환한다.

switch로 분석된 명령어에 따라 적절한 기능을 실행한다.
list로 전체 할 일 목록을 출력한다.
add로 사용자가 입력한 할 일을 추가한다.
pass로 문자열로 입력된 번호를 정수형으로 변환 후 완료 처리한다.
drop은 선택한 번호의 할일을 삭제한다.
마지막 end로 프로그램을 종료한다.
return을 사용해 run() 메서드를 종료한다.
무한 반복문이 함께 종료되며 프로그램 자체가 종료된다.

default를 통해 정의되지 않은 명령어를 입력하면 "잘못된 명령어"가 출력된다.

모든 명령어 실행 과정은 try-catch로 감싸져 있다.
예를 들어 drop 100처럼 존재하지 않는 번호를 입력하면 TodoList에서 발생한 예외가 여기서 처리된다.
그럼 프로그램은 종료되지 않고 계속 실행된다.

CommandHandler는 TodoList 프로젝트의 명령어 처리 담당 객체이다.
사용자가 입력한 문자열을 분석하고, 어떤 기능을 실행해야 하는지 결정한 뒤 Controller와 연결해준다.

todoApp

import java.util.Scanner;

public class todoApp {
    public static void main(String[] args) throws Exception {
        
        TodoList todoilst = new TodoList();
        View view = new View();
        Controller controller = new Controller(todoilst, view);

        Scanner scanner = new Scanner(System.in);
        CommandHandler commandHandler = new CommandHandler(controller, scanner);

        commandHandler.run();
        scanner.close();
    }
}

마지막으로 지금까지 만든 모든 객체를 연결하고 프로그램을 실행하는 TodoApp 클래스를 작성하였다.
이 클래스는 프로젝트의 시작점인 main() 메서드를 가지고 있으며 TodoList 애플리케이션을 실행하기 위한 객체들을 생성하고 연결하는 역할을 담당한다.

먼저 실제 할 일 데이터를 관리하는 객체를 생성한다.
이 객체는 할 일 추가, 삭제, 조회, 완료 처리와 같은 핵심 기능을 담당한다.

다음으로 화면 출력을 담당하는 View 객체를 생성한다.
View는 Controller로부터 전달받은 데이터를 사용자에게 출력하는 역할을 수행한다.

Controller는 TodoList와 View를 연결하는 중간 관리자 역할을 한다.
생성자를 통해 TodoList와 View 객체를 전달해 의존성 주입을 수행했다.

사용자 입력을 받기 위해 Scanner 객체를 생성한다.
사용자가 입력하는 명령어는 이 Scanner를 통해 읽어온다.

입력된 명령어를 분석하고 Controller와 연결하기 위해 CommandHandler 객체를 생성한다.
CommandHandler는 사용자와 프로그램 사이에서 명령어 처리 역할을 담당한다.

준비된 객체들을 이용해 프로그램을 실행한다.
run() 메서드가 호출되면 할 일 목록이 출력되고 이후 사용자의 명령어를 계속 입력받으며 프로그램이 동작한다.

프로그램이 종료되면 Scanner도 함께 종료한다.
사용이 끝난 자원을 정리하는 과정이다.

TodoList 마무리

이번 프로젝트에서는 간단한 TodoList 프로그램을 직접 구현해보면서 객체지향 프로그래밍의 기본 구조를 연습해보았다. 단순히 할 일을 추가하고 삭제하는 기능만 구현한 것이 아니라, TodoList, View, Controller, CommandHandler로 역할을 분리하여 각 클래스가 하나의 책임만 가지도록 설계했다.

또한 ArrayList를 활용한 데이터 관리, 예외 처리, 사용자 입력 처리, 의존성 주입, MVC 패턴의 기본 개념까지 함께 경험할 수 있었다. 규모는 작은 프로젝트이지만 실제 프로그램이 어떻게 동작하는지 이해하는 데 큰 도움이 되었으며, 앞으로 더 큰 프로젝트를 만들기 위한 좋은 기반이 되었다.

다음에는 연락처 관리 프로그램이나 도서 관리 프로그램처럼 객체를 활용하는 프로젝트를 만들어보면서 클래스 설계와 컬렉션 활용 능력을 더욱 발전시켜 볼 예정이다. 작은 프로젝트 하나를 완성했다는 것 자체가 의미 있는 경험이었고, 이번 과정을 통해 객체지향 프로그래밍에 한 걸음 더 익숙해질 수 있었다.


BucketList 만들기

BucketList

import java.util.ArrayList;
import java.util.List;

public class BucketList {
    
    private List<String> lists = new ArrayList<>();

    public BucketList() {
        addList("그리스 가기");
        addList("10억 벌기");
        addList("아파트 사기");
    }

    public List<String> getAll() {
        return lists;
    }

    public String getListByNumber(int number) {
        int index = number -1;
        if (index < 0 || index >= lists.size()) {
            throw new IllegalStateException("리스트를 찾을 수 없습니다");
        }
        return lists.get(index);
    }

    public void addList(String list) {
        if (list.trim().length() < 2) {
            throw new IllegalStateException("올바른 단어를 입력하세요");
        }
        lists.add(list);
    }

    public void removeList(int number) {
        int index = number -1;
        if (index < 0 || index >= lists.size()) {
            throw new IllegalStateException("리스트를 찾을 수 없습니다");
        }
        lists.remove(index);
    }

    public void passList(int number) {
        int index = number -1;
        if (index < 0 || index >= lists.size()) {
            throw new IllegalStateException("리스트를 찾을 수 없습니다");
        }
        if ((lists.get(index).startsWith("[완료]"))) {
            throw new IllegalStateException("이미 완료된 항목입니다");
        }
        lists.set(index, "[완료] " + lists.get(index));
    }
}

View

import java.util.List;

public class View {
    
    public View() {}

    public void showList(List<String> lists) {

        System.out.println();
        System.out.println("나의 버킷리스트");
        System.out.println("---------------");
        if (lists.size() < 1 ) {
            System.out.println("새 리스트를 추가해보세요");
        } else {
            for (int i = 0; i <lists.size(); i++) {
                System.out.println((i + 1) + ". " + lists.get(i));
            }
        }

        System.out.println("--------------------");
        System.out.println("|추가: add 리스트|삭제: drop 번호|완료: pass 번호|종료 end|");
    }
}

Controller

import java.util.List;

public class Controller {

    private BucketList bucketlist;
    private View view;

    public Controller(BucketList bucketlist, View view) {
        this.bucketlist = bucketlist;
        this.view = view;
    }

    public void listBucket() {
        List<String> lists = bucketlist.getAll();
        view.showList(lists);
    }

    public void addNewList(String list) {
        bucketlist.addList(list);
        List<String> lists = bucketlist.getAll();
        view.showList(lists);
    }

    public void dropList(int number) {
        bucketlist.removeList(number);
        List<String> lists = bucketlist.getAll();
        view.showList(lists);
    }

    public void passList(int number) {
        bucketlist.passList(number);
        List<String> lists = bucketlist.getAll();
        view.showList(lists);
    }
}

Command Handler

import java.util.Scanner;

public class CommandHandler {
    
    private Controller controller;
    private Scanner scanner;

    public CommandHandler(Controller controller, Scanner scanner) {
        this.controller = controller;
        this.scanner = scanner;
    }

    public void run() {

        controller.listBucket();

        while (true) {
            System.out.print("blist> ");
            String userInput = scanner.nextLine().trim();
            String[] parsedInput = userInput.split(" ", 2);
            String command = parsedInput[0];
            String arg = parsedInput.length > 1 ? parsedInput[1] : "";

            try {
            switch (command) {
                case "list":
                    controller.listBucket();
                    break;
                case "add" :
                    controller.addNewList(arg);
                    break;
                case "drop" :
                    controller.dropList(Integer.parseInt(arg));
                    break;
                case "pass" :
                    controller.passList(Integer.parseInt(arg));
                    break;
                case "end" : 
                    System.out.println("앱을 종료합니다");
                    return;
                    default: 
                    System.out.println("잘못된 명령어");
                }
            } catch (Exception e) {
                System.out.println("오류: " + e.getMessage());
            }
        }
    }
}

BucketlistApp

import java.util.Scanner;

public class BucketlistApp {
    public static void main(String[] args) throws Exception {
        
        BucketList bucketList = new BucketList();
        View view = new View();
        Controller controller = new Controller(bucketList, view);

        Scanner scanner = new Scanner(System.in);
        CommandHandler commandHandler = new CommandHandler(controller, scanner);

        commandHandler.run();
        scanner.close();
    }
}

Bucketlist 마무리

이번 프로젝트에서는 간단한 BucketList(버킷리스트) 프로그램을 직접 구현해보면서 Java 객체지향 프로그래밍의 기본 구조를 연습해보았다. 단순히 할 일을 추가하고 삭제하는 기능을 구현하는 것에서 끝나는 것이 아니라, BucketList, View, Controller, CommandHandler로 역할을 분리하여 각 클래스가 하나의 책임만 가지도록 설계했다.

또한 ArrayList를 활용한 데이터 관리, Scanner를 이용한 사용자 입력 처리, 예외 처리, 그리고 MVC 패턴의 기본 흐름까지 함께 경험할 수 있었다. 특히 입력을 CommandHandler에서 받아 Controller로 전달하고, 다시 View를 통해 출력하는 구조를 직접 연결하면서 프로그램이 어떻게 흐름을 가지는지 이해하는 데 도움이 되었다.

규모는 작은 프로젝트이지만 실제 콘솔 프로그램이 동작하는 방식을 체감할 수 있었고, 단순히 코드를 작성하는 것보다 “구조를 나누는 것”이 얼마나 중요한지 느낄 수 있었다. 또한 처음에는 입력 처리와 메서드 연결에서 많은 오류가 발생했지만, 하나씩 수정하면서 프로그램이 정상적으로 동작하는 과정을 통해 문제 해결 능력도 함께 향상되었다.

다음에는 연락처 관리 프로그램이나 도서 관리 프로그램처럼 객체를 더 적극적으로 활용하는 프로젝트를 만들어보면서 클래스 설계와 컬렉션 활용 능력을 더욱 발전시켜 볼 예정이다. 작은 프로젝트 하나를 완성했다는 것 자체가 의미 있는 경험이었고, 이번 과정을 통해 객체지향 프로그래밍에 한 걸음 더 익숙해질 수 있었다.

0개의 댓글