DTO와 VO

현서·2025년 5월 28일
1

자바

목록 보기
18/32
post-thumbnail

1. DTO (Data Transfer Object)

  • 데이터를 계층 간 또는 네트워크를 통해 전달하기 위해 사용하는 객체.

  • 주로 사용하는 곳: 컨트롤러 ↔ 서비스, 서비스 ↔ 레포지토리/DAO(Data Access Object) 간의 데이터 전달

  • 주요 특징:
    getter/setter가 모두 존재
    주로 계층 간의 데이터 전달 목적
    가변(mutable) 객체 → 데이터를 수정할 수 있음
    비즈니스 로직을 담지 않음 (순수한 데이터 전달용)

컨트롤러 (Controller)

  • 사용자의 요청을 가장 먼저 받아들이는 계층.
  • 웹 브라우저나 외부 클라이언트로부터 전달된 HTTP 요청(URL, GET/POST 등)을 받아 처리하는 역할을 한다.
  • 사용자의 요청을 파라미터로 받아 적절한 서비스 계층으로 전달하고, 서비스의 처리 결과를 응답으로 반환하는 역할을 한다.

서비스 (Service)

  • 컨트롤러와 레포지토리 사이에서 비즈니스 로직을 처리하는 핵심 계층.
  • 구체적인 로직이 이 계층에 위치하며, 여러 레포지토리를 조합하거나 조건에 따라 데이터를 가공하는 등의 로직이 들어간다.
  • 컨트롤러는 요청을 받아 서비스로 전달하고, 서비스는 로직을 수행한 후 그 결과를 컨트롤러에 반환한다.

레포지토리 (Repository)

  • 데이터베이스와 직접 연결되어 데이터를 저장, 조회, 수정, 삭제(CRUD)하는 역할을 한다.
  • JPA, MyBatis 등의 ORM 프레임워크와 연결되어 실제 데이터 접근을 추상화한다.
  • 서비스 계층은 레포지토리를 통해 원하는 데이터를 얻고 비즈니스 로직을 처리한다.

getter / setter

객체 지향 프로그래밍에서 클래스의 필드(속성)에 직접 접근하지 않고 간접적으로 접근할 수 있도록 도와주는 메서드.

  • Getter : 필드 값을 읽어오는 역할
  • Setter : 필드 값을 설정(변경)하는 역할
    일반적으로 private 필드를 외부에서 제어할 수 있도록 도와준다.
    -> 캡슐화(Encapsulation) 원칙을 지킬 수 있다.
    필요 시 내부 로직을 추가하여 값의 유효성 검사나 로깅 등도 구현할 수 있다.

영어 단어 사전 프로그램

package lesson07;

import java.time.LocalDate;
import java.util.*;

class WordDTO{
    private String word;
    private String meaning;
    private String level;
    private LocalDate regDate;

    public WordDTO(String word, String meaning, String level) {
        this.word = word;
        this.meaning = meaning;
        this.level = level;
        this.regDate = LocalDate.now();
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public String getMeaning() {
        return meaning;
    }

    public void setMeaning(String meaning) {
        this.meaning = meaning;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public LocalDate getRegDate() {
        return regDate;
    }

    public void setRegDate(LocalDate regDate) {
        this.regDate = regDate;
    }

    @Override
    public String toString() {
        return "[단어] " + word + " | 뜻: " + meaning + " | 레벨: " + level + " | 등록일: " + regDate;
    }
}

class WordController {
    private final WordService service = new WordService();

    public void register(String word, String meaning, int levelNum){
        String level = convertLevel(levelNum);
        if(level == null){
            System.out.println("잘못된 레벨입니다. (1: 초급, 2: 중급, 3: 고급)");
            return;
        }
        try{
            WordDTO dto = new WordDTO(word, meaning, level);
            service.registerWord(dto);
        }catch(IllegalArgumentException e){
            System.out.println("등록 실패: " + e.getMessage());
        }
    }

    public void printAllWords(){
        List<WordDTO> list = service.getAllWords();
        if(list.isEmpty()){
            System.out.println("등록된 단어가 없습니다.");
        }else{
            System.out.println("등록된 단어 목록");
            for(WordDTO dto : list){
                System.out.println("- " + dto);
            }
        }
    }

    public void query(String word){
        WordDTO dto = service.findWord(word);
        if(dto == null){
            System.out.println(word + "단어는 등록되어 있지 않습니다.");
        }else{
            System.out.println("조회 결과: " + dto);
        }
    }

    private String convertLevel(int levelNum){
        return switch (levelNum){
            case 1 -> "초급";
            case 2 -> "중급";
            case 3 -> "고급";
            default -> null;
        };
    }
}

class WordService{
    private final WordRepository repository = new WordRepository();
    public void registerWord(WordDTO dto){
        if(dto.getWord().isBlank() || dto.getMeaning().isBlank()){
            throw new IllegalArgumentException("단어와 뜻은 반드시 입력해야 합니다.");
        }
        repository.save(dto);
    }

    public List<WordDTO> getAllWords(){
        return repository.findAll();
    }

    public WordDTO findWord(String word){
        return repository.findByWord(word);
    }
}

class WordRepository{
//    private final HashMap<String, WordDTO> wordMap = new HashMap<>();
    private final Map<String, WordDTO> wordMap = new HashMap<>();

    public void save(WordDTO dto){
        wordMap.put(dto.getWord(), dto);
        System.out.println("저장 완료: " + dto.getWord());
    }

    public List<WordDTO> findAll(){
        return new ArrayList<>(wordMap.values());
    }

    public WordDTO findByWord(String word){
        return wordMap.get(word);
    }
}

public class Ex06_Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        WordController controller = new WordController();

        while(true){
            System.out.println("영어 단어 사전");
            System.out.println("1. 단어 등록");
            System.out.println("2. 전체 단어 목록 보기");
            System.out.println("3. 단어 상세 조회");
            System.out.println("0. 종료");
            System.out.print("선택: ");
            int choice = Integer.parseInt(sc.nextLine());

            switch(choice){
                case 0 -> {
                    System.out.println("프로그램을 종료합니다.");
                    sc.close();
                    return;
                }
                case 1 -> {
                    System.out.print("영단어 입력: ");
                    String word = sc.nextLine();
                    System.out.print("뜻 입력: ");
                    String meaning = sc.nextLine();
                    System.out.println("레벨 선택 (1: 초급, 2: 중급, 3: 고급): ");
                    int level = Integer.parseInt(sc.nextLine());
                    controller.register(word, meaning, level);
                }
                case 2 -> controller.printAllWords();
                case 3 -> {
                    System.out.println("조회할 영단어 입력: ");
                    String word = sc.nextLine();
                    controller.query(word);
                }
                default -> System.out.println("잘못된 메뉴입니다.");
            }
        }
    }
}

2. VO (Value Object)

  • 변경 불가능한 값 객체, 값 자체를 표현하기 위한 객체.

  • 주로 사용하는 곳: 주로 읽기 전용 데이터를 표현할 때 사용

  • 주요 특징:
    불변(immutable) 객체 → 한번 생성되면 상태를 바꿀 수 없음
    getter만 존재 (setter 없음)
    값 자체에 의미가 있음 (예: Money, Address)
    equals()와 hashCode() 재정의(오버라이드)하여 비교

배달 가능 지역 확인 시스템

package lesson07;

import java.util.Objects;
import java.util.Scanner;

class Point{
    private final int x;
    private final int y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }

    public double distanceTo(Point other){
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    public int getX() {return x;}
    public int getY() {return y;}

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if(!(obj instanceof Point)) return false;
        Point other = (Point) obj;
        return x == other.x && y == other.y;
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

class StoreService{
    private final Point storeLocation = new Point(0, 0);

    public boolean canDeliver(Point customerLocation) {
        double distance = storeLocation.distanceTo(customerLocation);
        System.out.println("거리 계산: " + distance);
        return distance <= 5.0;
    }
}

public class Ex07_Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StoreService service = new StoreService();
        System.out.println("가게 기준점은 (0, 0)입니다.");
        System.out.println("고객 위치 X좌표 입력: ");
        int x = Integer.parseInt(sc.nextLine());
        System.out.println("고객 위치 Y좌표 입력: ");
        int y = Integer.parseInt(sc.nextLine());

        Point customer = new Point(x, y);
        System.out.println("고객 위치: " + customer);

        if(service.canDeliver(customer)){
            System.out.println("배달 가능 지역입니다.");
        }else{
            System.out.println("배달 불가능한 지역입니다.");
        }

        sc.close();
    }
}

2. record

  • 자바 16부터 도입된 클래스 유형
  • 값을 담는 간단한 데이터 객체(VO, DTO 등)를 간결하게 정의할 수 있도록 도와준다.
  • 생성자, getter, toString(), equals(), hashCode() 등의 메서드를 자동으로 생성해준다.
  • 모든 필드는 자동으로 private final로 처리되어 불변(immutable) 객체가 된다.
  • 복잡한 코드 없이 데이터를 저장하고 비교하는 용도
  • 불필요한 보일러플레이트 코드를 줄이고 가독성과 유지보수성을 향상시킬 수 있다.

보일러플레이트 코드❓
: 어떤 기능을 만들 때 항상 비슷한 형태로 반복적으로 작성해야 하는 코드.
주로 프레임워크의 규칙이나 문법 때문에 필요하지만, 실제 로직은 거의 없는 코드들

1. 영어 단어 사전 프로그램 (recode 사용하여 변경)

package lesson07;

import java.time.LocalDate;
import java.util.*;

record Ex08_WordDTO(String word, String meaning, String level, LocalDate regDate){
    public Ex08_WordDTO(String word, String meaning, String level){
        this(word, meaning, level, LocalDate.now());
    }

    @Override
    public String toString() {
        return "[단어] " + word + " | 뜻: " + meaning + " | 레벨: " + level + " | 등록일: " + regDate;
    }
}

//컨트롤러
class Ex08_WordController {
    private final Ex08_WordService wordService = new Ex08_WordService();

    public void register(String word, String meaning, int levelNum) {
        String level = convertLevel(levelNum);
        if (level == null) {
            System.out.println("잘못된 레벨입니다.(1:초급, 2:중급, 3:고급)");
            return;
        }
        try {
            Ex08_WordDTO dto = new Ex08_WordDTO(word, meaning, level);
            wordService.registerWord(dto);
        } catch (IllegalArgumentException e) {
            System.out.println("등록 실패: " + e.getMessage());
        }
    }

    public void printAllWords() {
        List<Ex08_WordDTO> list = wordService.getAllWords();
        if (list.isEmpty()) {
            System.out.println("📂 등록된 단어가 없습니다.");
        } else {
            System.out.println("📘 등록된 단어 목록:");
            for (Ex08_WordDTO dto : list) {
                System.out.println("- " + dto);
            }
        }
    }

    public void query(String word) {
        Ex08_WordDTO dto = wordService.findWord(word);
        if (dto == null) {
            System.out.println("🔍 '" + word + "' 단어는 등록되어 있지 않습니다.");
        } else {
            System.out.println("🔎 조회 결과: " + dto);
        }
    }

    private String convertLevel(int levelNum) {
        return switch (levelNum) {
            case 1 -> "초급";
            case 2 -> "중급";
            case 3 -> "고급";
            default -> null;
        };
    }

}

//서비스 클래스
class Ex08_WordService {
    private final Ex08_WordRepository repository = new Ex08_WordRepository();

    public void registerWord(Ex08_WordDTO dto) {
        if (dto.word().isBlank() || dto.meaning().isBlank()) {
            throw new IllegalArgumentException("단어와 뜻은 반드시 입력해야 합니다.");
        }
        repository.save(dto);
    }

    public List<Ex08_WordDTO> getAllWords() {
        return repository.findAll();
    }

    public Ex08_WordDTO findWord(String word) {
        return repository.findByWord(word);
    }
}

//레파지토리 데이터 저장
class Ex08_WordRepository {
    private final Map<String, Ex08_WordDTO> wordMap = new HashMap<>();
//    private final HashMap<String, WordDTO> meaningMap = new HashMap<>();

    public void save(Ex08_WordDTO dto) {
        wordMap.put(dto.word(), dto);
        System.out.println("저장 완료: " + dto.word());
    }

    public List<Ex08_WordDTO> findAll() {
        return new ArrayList<>(wordMap.values());
    }

    public Ex08_WordDTO findByWord(String word) {
        return wordMap.get(word);
    }
}


public class Ex08_Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Ex08_WordController controller = new Ex08_WordController(); // con

        while (true) {
            System.out.println("영어 단어 사전");
            System.out.println("1. 단어 등록");
            System.out.println("2. 전체 단어 목록 보기");
            System.out.println("3. 단어 상세 조회");
            System.out.println("0. 종료");
            System.out.print("선택: ");
            int choice = Integer.parseInt(sc.nextLine());

            switch (choice) {
                case 0 -> {
                    System.out.println("프로그램을 종료합니다.");
                    sc.close();
                    return;
                }
                case 1 -> {
                    System.out.print("영단어를 입력: ");
                    String word = sc.nextLine();
                    System.out.print("뜻 입력: ");
                    String meaning = sc.nextLine();
                    System.out.print("레벨 선택(1:초급, 2:중급, 3:고급): ");
                    int level = Integer.parseInt(sc.nextLine());
                    controller.register(word, meaning, level);
                }
                case 2 -> controller.printAllWords();
                case 3 -> {
                    System.out.print("조회할 영단어 입력: ");
                    String word = sc.nextLine();
                    controller.query(word);
                }
                default -> System.out.println("잘못된 메뉴입니다.");
            }
        }
    }
}

2. 배달 가능 지역 확인 시스템 (recode 사용하여 변경)

package lesson07;

import java.util.Scanner;

record Ex09_Point(int x, int y){
    public double distanceTo(Ex09_Point other){
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

class Ex09_StoreService{
    private final Ex09_Point storeLocation = new Ex09_Point(0, 0);

    public boolean canDeliver(Ex09_Point customerLocation) {
        double distance = storeLocation.distanceTo(customerLocation);
        System.out.println("거리 계산: " + distance);
        return distance <= 5.0;
    }
}

public class Ex09_Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Ex09_StoreService service = new Ex09_StoreService();
        System.out.println("가게 기준점은 (0, 0)입니다.");
        System.out.println("고객 위치 X좌표 입력: ");
        int x = Integer.parseInt(sc.nextLine());
        System.out.println("고객 위치 Y좌표 입력: ");
        int y = Integer.parseInt(sc.nextLine());

        Ex09_Point customer = new Ex09_Point(x, y);
        System.out.println("고객 위치: " + customer);

        if(service.canDeliver(customer)){
            System.out.println("배달 가능 지역입니다.");
        }else{
            System.out.println("배달 불가능한 지역입니다.");
        }

        sc.close();
    }
}
profile
The light shines in the darkness.

0개의 댓글