상세 컨텐츠

본문 제목

DTO와 VO

백엔드/Java

by Ryuzy 2025. 5. 23. 11:52

본문

반응형

1. DTO

DTO(Data Transfer Object)는 계층 간 또는 네트워크를 통한 데이터 전달을 위해 사용되는 객체로, 주로 컨트롤러와 서비스, 서비스와 레포지토리 간에 데이터를 주고받을 때 사용됩니다. DTO는 데이터 전달에 목적이 있으므로 비즈니스 로직을 포함하지 않으며, 필요한 데이터만 골라 담아 전송 효율성과 보안성을 높일 수 있습니다. 일반적으로 불변 객체는 아니며, getter와 setter 메서드를 포함하여 유연하게 데이터를 조작할 수 있도록 설계됩니다.

 

컨트롤러 (Controller)

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

 

서비스 (Service)

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

 

레포지토리 (Repository)

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

 

getter / setter

Getter와 Setter는 객체 지향 프로그래밍에서 클래스의 필드(속성)에 직접 접근하지 않고 간접적으로 접근할 수 있도록 도와주는 메서드입니다. Getter는 필드 값을 읽어오는 역할, Setter는 필드 값을 설정(변경)하는 역할을 하며, 일반적으로 private 필드를 외부에서 제어할 수 있도록 도와줍니다. 이를 통해 캡슐화(Encapsulation) 원칙을 지킬 수 있으며, 필요 시 내부 로직을 추가하여 값의 유효성 검사나 로깅 등도 구현할 수 있습니다.

 

영어 단어 사전 프로그램

import java.time.LocalDate;

public 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 String getMeaning() { return meaning; }
    public String getLevel() { return level; }
    public LocalDate getRegDate() { return regDate; }

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

 

import java.util.List;

public 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 WordDTO findWord(String word) {
        return repository.findByWord(word);
    }

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

 

import java.util.*;

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

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

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

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

 

import java.util.List;

public 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;
        };
    }
}

 

import java.util.Scanner;

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

        while (true) {
            System.out.println("\n=== 영어 단어 사전 ===");
            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. VO

VO(Value Object)는 값 자체로 객체의 동일성을 판단하는 객체로, 주로 도메인 모델에서 의미 있는 데이터를 표현할 때 사용됩니다. VO는 일반적으로 불변 객체로 설계되며, 내부 필드는 final로 선언하고, 생성자를 통해 값을 설정한 후 변경할 수 없습니다. 값이 같으면 같은 객체로 간주되기 때문에 equals()와 hashCode() 메서드를 반드시 오버라이드해야 하며, 주소, 좌표, 통화(Money) 등과 같이 실세계의 속성을 표현하는 데 자주 사용됩니다. VO는 객체의 식별자보다는 그 안에 담긴 데이터의 내용 자체가 중요할 때 적합합니다.

 

배달 가능 지역 확인 시스템

import java.util.Objects;

public 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; }

    // VO의 핵심: 값이 같으면 동일한 객체로 간주
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return x == other.x && y == other.y;
    }

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

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

 

public class StoreService {
    private final Point storeLocation = new Point(0, 0); // 가게 위치 기준점

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

 

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StoreService service = new StoreService();

        System.out.println("🏠 가게 기준점은 (0, 0)입니다.");
        System.out.print("고객 위치 X 좌표 입력: ");
        int x = Integer.parseInt(sc.nextLine());

        System.out.print("고객 위치 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

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

 

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

// WordDTO.java (record)
import java.time.LocalDate;

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

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

// WordRepository.java
import java.util.*;

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

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

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

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

// WordService.java
import java.util.List;

public class WordService {
    private final WordRepository repository = new WordRepository();

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

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

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

// WordController.java
import java.util.List;

public 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;
        };
    }
}

// Main.java
import java.util.Scanner;

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

        while (true) {
            System.out.println("\n=== 영어 단어 사전 ===");
            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. 배달 가능 지역 확인 시스템

// Point.java (record for VO)
public record Point(int x, int 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);
    }

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

// StoreService.java
public class StoreService {
    private final Point storeLocation = new Point(0, 0); // 가게 위치 고정

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

// DeliveryMain.java
import java.util.Scanner;

public class DeliveryMain {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StoreService storeService = new StoreService();

        System.out.println("🏪 가게 위치는 (0, 0)입니다.");

        System.out.print("고객 위치 X 입력: ");
        int x = Integer.parseInt(sc.nextLine());

        System.out.print("고객 위치 Y 입력: ");
        int y = Integer.parseInt(sc.nextLine());

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

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

        sc.close();
    }
}

 

반응형

'백엔드 > Java' 카테고리의 다른 글

스트림  (0) 2025.05.25
람다식  (1) 2025.05.25
제네릭  (0) 2025.05.23
java.lang 패키지  (0) 2025.05.21
예외처리  (0) 2025.05.20

관련글 더보기