풀스택 개발자 과정 22일차

너구·2026년 6월 8일

풀스택 성장과정

목록 보기
22/79

DTO 생성하기 예제

데이터베이스로부터 가져온 Post 리스트로부터 PostListDto 리스트를 생성하는 코드를 작성하라.

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

// Post(원본 게시물)
class Post {

    private int id;
    private String title;
    private String thumbnailUrl;
    private String content;

    public Post(int id, String title, String thumbnailUrl, String content) {
        this.id = id;
        this.title = title;
        this.thumbnailUrl = thumbnailUrl;
        this.content = content;
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getThumbnailUrl() {
        return thumbnailUrl;
    }

    public String getContent() {
        return content;
    }
}

// PostListDto(전송용)
class PostListDto {

    private String thumbnailUrl;
    private String title;

    public PostListDto(String thumbnailUrl, String title) {
        this.thumbnailUrl = thumbnailUrl;
        this.title = title;
    }

@Override
public String toString() {
    return String.format
    ("PostListDto{thumbnailUrl=%s, title=%s}", thumbnailUrl, title);
    }
}

public class Main {
    public static void main(String[] args) {
        
        List<Post> posts = new ArrayList<>();

        // DB에서 가져온 원본 게시물 데이터
        posts.add(new Post(1, "1번째 게시물", "thumbnail_1", "1번째 게시글 내용"));
        posts.add(new Post(2, "2번째 게시물", "thumbnail_2", "2번째 게시물 내용"));
        posts.add(new Post(3, "3번째 게시물", "thumbnail_3", "3번째 게시물 내용"));

        List<PostListDto> postListDtos = posts.stream()
        .map(post -> new PostListDto(post.getThumbnailUrl(), post.getTitle()))
        .collect(Collectors.toList());

        for(PostListDto postListDto : postListDtos) {

        System.out.println(postListDto);
        }
    }
}

이 코드는 Post 데이터를 화면에 필요한 형태(PostListDto)로 변환하는 과정을 보여주는 예제이다.
먼저 Post 클래스는 게시물의 전체 정보를 담고 있다.
게시물 번호, 제목, 썸네일 주소, 내용까지 모두 가지고 있는 원본 데이터이다.
실제로 데이터베이스에서 조회한 데이터를 표현한다고 생각하면 된다.

반면 PostListDto는 사용자에게 전달할 정보만 담는 DTO이다.
게시물 목록 화면에서는 보통 제목과 썸네일만 필요하므로 필요한 데이터만 따로 담는다.

main()에서는 먼저 게시물 데이터를 생성한다.
게시물들을 저장할 리스트를 만들고 posts.add(new Post(...));를 통해 데이터베이스에서 가져온 것처럼 게시물 데이터를 저장한다.

그 다음 posts리스트를 stream으로 변환한다.
map()을 통해 데이터를 다른 형태로 변환시킨다.
Post -> PostListDto로 변환한다.
필요없는 id, content는 제거하고 필요한 데이터만 남긴다.

그 다음 collect()를 통해 변환된 DTO들을 다시 List 형태로 모은다.
결과적으로 List< PostListDto > postListDtos가 생성된다.

마지막으로 for문을 통해 DTO 리스트를 순회하면서 println로 postListDto를 실행한다.
toString()이 오버라이딩 되어있기 때문에 객체의 주소값이 아니라 DTO의 정보가 출력된다.


Set 활용하기 예제

열거형 Roles를 값으로 가지는 집합 myRoles를 생성하고 역할(Role)을 추가하라.

import java.util.HashSet;
import java.util.Set;

// 역할 (Role) 종류
enum Roles { ROLE_ADMIN, ROLE_STAFF, ROLE_USER }
// ADMIN: 관리자(Administrator)
// STAFF: 직원
// User: 일반 사용자

public class Main {
    public static void main(String[] args) {
        // 사용자의 역할을 담는 집합
        // Set - 중복 안 됨, 순서가 없음
        Set<Roles> myRoles = new HashSet<>();

        // 역할 추가
        myRoles.add(Roles.ROLE_STAFF);
        myRoles.add(Roles.ROLE_STAFF);
        myRoles.add(Roles.ROLE_STAFF);
        myRoles.add(Roles.ROLE_USER);

        System.out.println(myRoles);

    }
}

먼저 enum을 사용해 역할 종류를 정의한다.
enum은 정해진 상수들을 하나의 타입으로 관리하는 기능이다.
역할은 정해진 값만 사용해야하므로 String 대신 enum을 사용하는 것이 안전하다.

다음으로 역할을 저장할 Set을 생성한다.
Set은 중복을 허용하지 않는 자료구조이다.
즉 같은 값을 여러번 추가해도 하나만 저장된다.

그 다음 add로 역할을 추가한다.

마지막으로 Set을 출력한다.

이 예제의 핵심은 Enum으로 역할을 정의하고 Set으로 중복없이 관리하는 것이다.
실무에서는 사용자 권한을 관리할 때 자주 사용하는 방식이며, 같은 권한이 여러 번 저장되는 것을 방지할 수 있다. 또한 Enum을 사용하면 오타로 인한 오류를 줄이고 코드의 안정성을 높일 수 있다.

Map으로 DTO 만들기 예제

토큰 하나를 전송하는 간단한 서버의 응답에서 DTO 대신 Map을 활용하라.

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        
        // dto (데이터 전송 객체) 역할
        Map<String, String> reponse = new HashMap<>();

        reponse.put("accessToken", "xxx");

        System.out.println(reponse);
    }
}

먼저 Map 객체를 생성한다.
Map은 데이터를 키(Key)와 값(Value) 형태로 저장하는 자료구조이다.
여기서는 Key와 Value 둘 다 String 형태로 저장하도록 선언하였다.

다음으로 데이터를 추가한다.
put() 메서드는 Map에 데이터를 저장하는 메서드이다.
여기서는 키(Key) : accessToken, 값(Value) : xxx 를 저장한다.
실제로는 로그인 성공 후 발급된 토큰을 저장한다고 생각하면 된다.

마지막으로 Map을 출력한다.
{accessToken=xxx} 출력 결과는 이렇다.

Java 미니 프로젝트

플레이리스트 만들기

이런식으로 cmd에서 작동이 되는 플레이리스트 앱을 만들어보도록 할 거다.

  • 객체 개요

  • PlayList 변수/ 메서드

  • View 메서드

  • Controller 변수/ 메서드

  • CommandHandler 변수/ 메서드

Playlist.java

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

// 데이터(곡)을 다루는 객체
public class Playlist {
    
    // 곡 목록 데이터
    private List<String> songs = new ArrayList<>();

    public Playlist() {
        // 샘플 곡 추가하기
        addSong("Lately - Stevie Wonder");
        addSong("Ordinary People - John Legend");
        addSong("Man In The Mirror - Michael Jackson");
    }

    // 모든 노래 가져오기
    public List<String> getAllSongs() {
        return songs;
    }

    // 입력받은 번호에 맞는 곡 가져오기
    public String getSongByNumber(int number) {
        // 번호로부터 인덱스를 구합니다
        int index = number -1; // 인덱스가 0부터 시작하기 때문에 1을 뺀다
        if (index < 0 || index >= songs.size()) { // 인덱스의 범위가 올바른지 검사
            throw new IllegalStateException("곡을 찾을 수 없습니다");
        }
        return songs.get(index); // 실제로 곡에 접근
        // get(인덱스): 인덱스에 해당하는 요소를 반환
    }

    // 노래 추가하기
    public void addSong(String song) { // song: 새로 추가할 노래
        if (song.trim().length() < 3) { // 노래 제목이 너무 짧음
            // trim(): 문자열에서 앞/뒤에 있는 공백 제거
            throw new IllegalStateException("올바른 제목을 입력하세요");
        }
        songs.add(song); // 리스트에 곡 추가
    }

    // 노래 제거하기
    public void removeSong(int number) { // number: 제거할 노래의 번호
        int index = number -1;
        if (index < 0 || index >= songs.size()) { // 잘못된 번호를 입력한 경우
            throw new IllegalStateException("곡을 찾을 수 없습니다");
        }
        songs.remove(index);
    }
}

이 코드는 재생목록을 관리하는 Playlist 클래스를 구현한 예제이다.
노래를 저장하고, 조회하고, 추가하고, 삭제하는 기능을 하나의 클래스에 모아두었다.
먼저 songs라는 리스트를 생성해 노래 목록을 저장한다.
이 리스트가 실제 재생목록 역할을 하며, 추가된 모든 노래가 여기에 저장된다.
생성자에서는 재생목록 객체가 만들어질 때 기본적으로 사용할 샘플 곡 3개를 추가한다.
따라서 new Playlist()를 실행하면 빈 목록이 아니라 이미 3곡이 들어있는 상태로 시작하게 된다.
getAllSongs() 메서드는 현재 저장된 모든 노래 목록을 반환한다.
getSongByNumber()는 사용자가 입력한 번호에 해당하는 노래를 가져오는 기능을 한다.
리스트의 인덱스는 0부터 시작하기 때문에 사용자가 입력한 번호에서 1을 빼서 실제 인덱스로 변환한다.
그리고 다음 코드를 통해 번호가 올바른 범위인지 검사한다.
존재하지 않는 번호를 입력하면 예외를 발생시켜 잘못된 접근을 막는다.
노래를 추가하는 기능은 addSong() 메서드가 담당한다.
입력된 제목의 앞 뒤 공백을 제거한 뒤 길이가 3글자 미만이면 예외를 발생시킨다.
제목이 정상적인 경우에만 리스트에 추가된다.
removeSong()은 특정 번호의 노래를 삭제하는 기능이다.
마찬가지로 번호를 인덱스로 변환한 뒤 범위를 검사한다.
번호가 올바르면 remove()를 통해 해당 노래를 삭제한다.

View

import java.util.List;

// UI(사용자 인터페이스) 역할 - 화면, 보여지는 부분
public class View {

    public View() {}

    // 곡 목록 출력하기 (메인 화면)
    public void showList(List<String> songs) { // songs: 출력할 곡 목록

        System.out.println();
        System.out.println("나의 플레이리스트");
        System.out.println("-------------------------");
        if (songs.size() < 1) { // 노래가 없으면
            System.out.println("새 곡을 추가해보세요");
        } else { // 노래가 있으면 목록을 출력
            for (int i = 0; i < songs.size(); i++) { // i는 인덱스 역할
                System.out.println((i + 1) + ". " + songs.get(i)); // i + 1: 보여지는 번호를 만든다
            }
        }

        // 명령어들을 보여준다
        System.out.println("--------------------------");
        System.out.println("|추가: add 노래|삭제: drop 번호|재생: play 번호|종료: end|");
    }

    // 곡 재생화면
    public void showMusic(String song) { // song: 재생할 노래

        System.out.println();
        System.out.println("나의 플레이리스트");
        System.out.println("--------------------------");
        System.out.println(song);
        System.out.println("--------------------------");
        System.out.println("|돌아가기: list|종료: end|");
    }
}

이 코드는 플레이 리스트 프로그램에서 화면을 담당하는 View 클래스이다.
앞에서 작성한 Playlist 클래스가 노래 데이터를 관리하는 역할이었다면 View 클래스는 사용자에게 데이터를 보내주는 역할을 담당한다.
즉 Playlist는 데이터 관리, View는 화면 출력으로 역할을 분리한 것이다.

showList() 메서드는 메인 화면을 출력하는 기능이다.
매개변수로 노래 목록을 전달받고 제목과 구분선을 출력한다.
그 다음 노래가 있는지 확인하고 if문으로 노래가 없다면 "새 곡을 추가해보세요"를 출력한다.
반대로 노래가 있다면 for문을 통해 목록을 출력한다.
리스트의 모든 요소를 순회하며 System.out.println((i + 1) + ". " + songs.get(i));를 실행한다.
여기서 i는 인덱스이므로 0부터 시작한다.
하지만 사용자는 보통 1. 2. 3. 처럼 번호를 보기 때문에 i + 1을 사용해 표시용 번호를 만든다.
목록 출력이 끝나면 사용 가능 명령어를 보여준다.
사용자는 이 명령어를 보고 어떤 기능을 사용할 수 있는지 확인할 수 있다.
showMusic() 메서드는 노래 재생 화면을 출력하는 기능이다.
재생할 노래 목록을 전달받고 화면 제목과 구분선을 출력한 뒤 현재 재생중인 노래를 보여준다.
그리고 마지막에 돌아가기 종료를 출력해 사용자가 목록으로 돌아가거나 프로그램을 종료할 수 있도록 안내한다.

이 클래스의 핵심은 데이터를 직접 관리하지 않고 화면 출력만 담당한다는 점이다.
이처럼 역할을 분리하면 코드가 깔끔해지고 유지보수가 쉬워진다.
나중에 화면 구성을 변경하더라도 View만 수정하면 되고 노래 관리 기능은 건드리지 않아도 된다.
이는 객체지향 프로그래밍에서 중요한 역할 분리의 좋은 예시이다.

Controller

import java.util.List;

public class Controller {

    // Playlist, View 호출
    private Playlist playlist;
    private View view;

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

    // 곡 목록 처리
    public void listSongs() {
        // Playlist(데이터 접근 객체)로부터 곡 목록을 가져온다
        List<String> songs = playlist.getAllSongs();
        // 뷰에게 목록을 전달한다
        view.showList(songs);
    }

    // 곡 재생 처리
    public void playSong(int number) { // number: 전달받은 곡의 번호
        // 번호로부터 곡을 가져온다
        String song = playlist.getSongByNumber(number);
        // 가져온 곡을 뷰에게 전달
        view.showMusic(song);
    }

    // 곡 추가 처리
    public void addNewSong(String song) { // song: 추가할 노래
        // 곡을 리스트에 추가
        playlist.addSong(song);
        // 리스트를 가져온다 (새 곡이 저장된 상태)
        List<String> songs = playlist.getAllSongs();
        // 뷰에게 리스트를 전달
        view.showList(songs);
    }

    // 곡 삭제 담당
    public void dropSong(int number) { // number: 삭제할 곡의 번호
        // 곡 리스트에서 번호에 해당하는 곡을 삭제
        playlist.removeSong(number);
        // 갱신된 리스트
        List<String> songs = playlist.getAllSongs();
        // 갱신된 리스트를 뷰에게 전달
        view.showList(songs);
    }
}

이 코드는 플레이리스트 프로그램의 Controller 역할을 담당하는 클래스이다.
앞에서 만든 Playlist는 데이터를 관리하고 View는 화면을 출력했다.
Controller는 그 사이에서 사용자의 요청을 받아 적절한 작업을 수행하는 역할을 한다.

먼저 Controller는 Playlist와 View 객체를 가지고 있다.
Playlist의 기능을 사용하기 위해 playlist를 저장하고 화면을 출력하기 위해 view를 저장한다.
생성자에서는 두 객체를 전달받는다.
이 과정을 의존성 주입(DI)라고 한다.
메인 화면의 곡 목록을 출력하는 기능을 만든다. listsong()
먼저 Playlist에게 전체 곡 목록을 요청한다.
그 다음 View에게 목록을 전달한다.
즉 Controller는 데이터를 가져와서 화면에 전달만 한다.
그 다음 playSong으로 사용자가 선택한 번호의 노래를 재생하는 기능을 만든다.
먼저 번호에 해당하는 곡을 가져오고 그 다음 View에게 전달해 재생 화면을 출력한다.
그 다음 addNewSong으로 새로운 곡을 추가하는 기능을 만든다.
먼저 playlist에 노래를 저장하고 추가가 완료되면 최신 목록을 다시 가져온다.
그리고 View를 통해 갱신된 목록을 출력한다.
그 다음 dropSong으로 곡 삭제 기능을 만든다.
선택한 번호의 곡을 삭제하는 기능이다.
먼저 Playlist에 삭제를 요청하고 삭제가 끝나면 다시 전체 목록을 가져온다.
그리고 View를 통해 변경된 목록을 출력한다.

이 클래스의 가장 중요한 역할은 Playlist와 View를 연결하는 것이다.
Controller는 직접 데이터를 저장하지도 않고 화면을 출력하지도 않는다.
대신 사용자의 요청에 따라 데이터 가져오기 -> 데이터 수정하기 -> 화면 갱신하기 과정을 수행한다.
즉 MVC 패턴에서 Controller의 역할을 가장 단순하게 구현한 예제이며 프로그램의 흐름을 제어하는 핵심 클래스라고 볼 수 있다.

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.listSongs();

        // 앱을 종료할 때까지 무한히 반복
        while (true) {
            // 사용자 입력란을 출력 (plist>)
            System.out.print("plist> ");
            // 사용자 입력을 문자열(nextLine 메서드)로 저장
            String userInput = scanner.nextLine().trim(); // trimg(): 문자열 앞/뒤 공백 제거
            // 사용자 입력을 띄어쓰기를 기준으로 2부분으로 나눈다
            // limit: 띄어쓰기 기준으로 나누는 갯수를 지정
            String[] parsedInput = userInput.split(" ", 2);
            // 명령어 (add, drop 등)
            String command = parsedInput[0];
            // 명령어와 함께 입력된 인자(arguments)
            String arg = parsedInput.length > 1 ? parsedInput[1] : "";
            // parsedInput.length > 1 = 명령어와 함께 입력된 인자가 있다
            // 인자가 있으면 그 인자를 쓰고 없으면 빈 문자열을 저장

            // 명령어와 그 명령어에 적합한 로직 호출
            try {
            switch (command) { // command: 사용자가 입력한 명령어
                case "list":
                    controller.listSongs();
                    break;
                case "play": // Integer.parseInt(문자열 숫자): 문자열 숫자를 정수형으로 변환
                    controller.playSong(Integer.parseInt(arg)); // arg: 재생할 곡의 번호 (문자열 상태)
                    break;// "1"(문자열) -> 1 (정수형)
                case "add":
                    controller.addNewSong(arg); // arg: 노래 제목
                    break;
                case "drop":
                    controller.dropSong(Integer.parseInt(arg)); // arg: 삭제할 곡의 번호 (문자열 상태)
                    break; // switch의 break (while의 break가 아님)
                case "end":
                    System.out.println("앱을 종료합니다");
                    return; // run()을 빠져나온다
                    default: // 위에 있는 명령어가 아닌 경우
                        System.out.println("잘못된 명령어");
                }
            } catch (Exception e) { // 예외 처리 - 모든 객체가 던지는 예외를 처리
                System.out.println("오류: " + e.getMessage());
            }
        }
    }
}

이 코드는 사용자가 입력한 명령어를 분석하고 그에 맞는 Controller의 기능을 실행하는 역할을 담당한다.
즉 사용자가 입력한 명령어를 가장 먼저 받는 클래스가 CommandHandler다.

먼저 Controller와 Scanner 객체를 저장한다.
controller는 실제 기능 실행을 담당하고 scanner는 사용자 입력을 담당한다.
생성자에서는 두 객체를 전달받아 저장한다.
run() 메서드는 프로그램의 시작점 역할을 한다.
먼저 앱이 실행되면 재생목록을 출력한다.
따라서 사용자는 프로그램을 실행하자마자 현재 노래 목록을 볼 수 있다.
그 다음 무한 반복문이 시작된다.
이 반복문은 사용자가 종료 명령을 입력할 때까지 계속 실행된다.
사용자에게 입력창을 보여준다.
실행 화면에서는 plist> 이렇게 보인다.
그 다음 사용자가 입력한 값을 읽어온다.
예를 들어 사용자가 play 2를 입력하면 userInput = "play 2" 가 된다.
trim()은 앞뒤 공백을 제거하는 역할을 한다.
String[] parsedInput = userInput.split(" ", 2); 이 코드는 입력값을 분석하는 부분이다.
공백을 기준으로 최대 2개로 나눈다.
예를들어 play 2는 play와 2로 나뉜다.
그 다음 명령어와 인자를 분리하여 명령어를 저장한다.
명령어 뒤에 값이 있으면 저장하고 없으면 빈 문자열을 저장한다.
command = "play" arg = "2" 이런식이 된다.
이제 switch 문을 사용해 명령어를 처리한다.
case list로 현재 플레이 리스트를 출력하고 case play로 입력받은 번호를 정수로 변환한 뒤 해당곡을 재생한다.
그 다음 case add로 곡 추가가 가능하게 만들고 case drop으로 삭제가 가능하게 만들었다.
그 다음 end로 return을 만나면 run() 메서드가 종료되고 프로그램도 종료되게 만들었다.
예외처리도 포함되어있다.
사용자가 play abc 처럼 잘못 입력하거나 존재하지 않는 곡 번호를 입력하면 예외가 발생할 수 있다.
이 때 System.out.println("오류: " + e.getMessage());를 실행해 프로그램이 종료되지 않고 오류 메시지만 출력한다.

이 클래스의 역할은 사용자의 입력을 받아 적절한 Controller 기능과 연결하는 것이다.
즉, CommandHandler는 사용자의 명령어를 해석하고 Controller에게 작업을 요청하는 명령어 처리기(Command Processor) 역할을 수행하는 클래스이다.

PlaylistApp

import java.util.Scanner;

public class PlaylistApp {
    public static void main(String[] args) throws Exception {
        
        // Controller
        Playlist playlist = new Playlist();
        View view = new View();
        Controller controller = new Controller(playlist, view);

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

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

    }
}

이 코드는 지금까지 만든 모든 객체를 연결하고 프로그램을 실제로 시작하는 시작점 (Main 클래스)이다.
즉 플레이리스트 프로그램의 전체 구조를 조립하는 역할을 담당한다.

먼저 Playlist 객체를 생성한다.
이 객체는 노래 목록을 저장하고 관리하는 역할을 한다.
곡 추가 곡 삭제 곡 조회와 같은 기능이 모두 Playlist 안에 구현되어 있다.
다음으로 View 객체를 생성한다.
View는 플레이리스트 출력, 재생 화면 출력과 같은 사용자에게 보여지는 부분을 처리한다.
그 다음 Controller를 생성한다.
Controller는 Playlist와 View를 연결하는 역할을 한다.
사용자 입력을 받기 위한 Scanner도 생성한다.
사용자가 입력하는 명령어를 읽어오는 객체이다.
다음으로 CommandHandler를 생성한다.
CommandHandler는 사용자의 명령어를 해석하는 역할을 한다.
이제 commandHandler.run();으로 프로그램을 실행한다.
여기서부터 무한 반복문이 실행되며 사용자의 명령어를 계속 처리한다.
plist> 입력창이 나타나고 사용자가 명령어를 입력할 수 있게 된다.
프로글매이 종료되면 scanner.close();로 Scanner를 닫는다.

이 코드는 지금까지 만든 객체들을 모두 연결하는 역할을 한다.

PlaylistApp(Main)

├── Playlist
├── View
├── Controller
├── Scanner
└── CommandHandler


run()


프로그램 시작

PlaylistApp은 플레이리스트 프로그램의 진입점이다.

Playlist → 데이터 관리
View → 화면 출력
Controller → 기능 연결
CommandHandler → 명령어 처리
PlaylistApp → 객체 생성 및 실행

즉, 지금까지 만든 모든 클래스를 조립해서 실제로 동작하는 플레이리스트 프로그램을 완성하는 마지막 단계라고 볼 수 있다.


마무리

오늘은 단순히 컬렉션 문법을 배우는 것을 넘어, 작은 프로그램이 어떻게 구조적으로 만들어지는지 경험할 수 있었다.

초반에는 ArrayList, Set, Map을 사용해 데이터를 저장하고 조회하는 방법을 배웠다. ArrayList는 순서가 있는 데이터 관리에 적합하고, Set은 중복을 허용하지 않는다는 점, Map은 키와 값을 연결해 데이터를 관리한다는 점을 직접 코드로 확인할 수 있었다. 또한 Stream을 활용해 데이터를 필터링하고 DTO로 변환하는 과정도 연습하면서 실제 서비스에서 데이터를 가공하는 방식이 어떤 것인지 이해할 수 있었다.

특히 DTO 생성 예제는 인상적이었다. 원본 객체를 그대로 전달하는 것이 아니라 필요한 데이터만 추출해서 새로운 객체로 변환하는 과정을 통해 왜 DTO가 필요한지 알 수 있었다. map()과 collect()를 사용해 객체를 다른 형태로 변환하는 과정은 앞으로 백엔드 개발을 할 때 자주 사용될 중요한 패턴이라는 생각이 들었다.

후반부에는 플레이리스트 프로젝트를 만들면서 객체들의 역할 분리가 얼마나 중요한지 배울 수 있었다. Playlist는 데이터를 관리하고, View는 화면을 출력하며, Controller는 비즈니스 로직을 처리하고, CommandHandler는 사용자의 입력을 분석하는 역할을 담당했다. 각 클래스가 하나의 책임만 가지도록 분리하니 코드의 흐름이 훨씬 명확해졌고 유지보수도 쉬워진다는 것을 체감할 수 있었다.

또한 생성자를 통한 의존성 주입, 예외 처리, 컬렉션 활용, 객체 간 협력 구조까지 자연스럽게 경험할 수 있었다. 이전에는 하나의 클래스 안에서 모든 기능을 구현하는 방식에 익숙했지만, 이제는 기능별로 클래스를 나누고 서로 협력하게 만드는 방법을 조금씩 이해하게 된 것 같다.

오늘 학습을 통해 단순히 자바 문법을 익히는 것을 넘어 실제 프로그램이 어떤 구조로 만들어지는지 배울 수 있었고, 객체지향 프로그래밍의 중요성을 느낄 수 있는 시간이었다. 앞으로는 지금까지 배운 컬렉션, 스트림, DTO, 예외 처리, 클래스 분리 개념들을 활용해 더 복잡한 프로그램도 직접 만들어 보며 경험을 쌓아가고 싶다.

0개의 댓글