[TIL 5일 차] 객체지향 프로그래밍의 이해

오늘의 학습

1. 내부 클래스와 익명 클래스

1-01. 내부 클래스(Inner Class)

자바에서 내부 클래스란 클래스 내부에 선언된 또 다른 클래스를 말함

외부 클래스의 멤버(필드/메서드)에 자유롭게 접근할 수 있어, 두 클래스가 긴밀히 협력해야 할 때 밀접하게 관련된 기능을 함께 묶어 설계할 때 사용

class Outer { // 외부 클래스

  class Inner {
    // 인스턴스 내부 클래스
  }
}


1) 내부 클래스를 사용하는 이유

  • 외부 클래스와 밀접한 기능을 묶음 → 응집도 증가
  • 캡슐화(정보 은닉) 강화
  • 가독성 증가 및 유지보수 향상


2) 내부 클래스 종류

구분 선언 위치 외부 클래스 접근 가능 여부 특징
인스턴스 내부 클래스 외부 클래스의 멤버 위치 외부 인스턴스 변수 접근 가능 외부 클래스와 강한 결합
정적(static) 내부 클래스 외부 클래스의 멤버 위치 외부의 static 멤버만 접근 가능 static 키워드 사용, 독립성 높음
지역 내부 클래스 외부 클래스의 메서드 또는 초기화 블록 내부 final 또는 effectively final 지역 변수 접근 가능 메서드 내에서만 사용
익명 내부 클래스 클래스 정의와 인스턴스 생성을 동시에 외부 클래스 멤버 접근 가능 이름 없는 일회성 클래스


1-02. 익명 클래스(Anonymous Class)

이름이 없는 클래스로, 일회성으로 사용될 클래스를 정의할 때 유용하며, 주로 인터페이스나 추상 클래스의 구현체를 즉석에서 정의할 때 사용됨

짧고 간단한 작업을 빠르게 구현할 수 있는 도구로, 이벤트 처리나 콜백 처리, 임시 구현 등에서 자주 활용


1) 익명 클래스의 기본 형식

인터페이스타입 변수 = new 인터페이스타입() {
    // 추상 메서드 구현
};
  • 또는 추상 클래스의 경우:
추상클래스 변수 = new 추상클래스() {
    // 추상 메서드 구현
};


2) 익명 클래스의 한계 및 주의점

  • 단 한 번만 사용되며 재사용이 어려움
  • 내부에서는 외부 메서드의 지역 변수에 접근할 수 있지만, 해당 지역 변수는 반드시 final 또는 effectively final이어야 함
  • 여러 개의 메서드가 있는 인터페이스/추상 클래스는 구현 코드가 길어질 수 있어 가독성이 떨어질 수 있음

2. Enum (열거 타입)의 활용

2-01. Enum 핵심 원리

Enum (enumerated type; 열거형)여러 상수들을 보다 편리하게 선언하기 위한 문법 요소로, 내부적으로 클래스로 변환되어 동작하기 때문에 필드, 메서드, 생성자를 모두 가질 수 있다.

  • 상수 집합
  • 다만 필드가 있다면 생성자를 통해서 값을 주입하여 필드 불변성 유지
  • 싱글톤처럼 동작하기에 == 비교 가능

1) 기본 형태

public enum OrderStatus {
    READY,
    IN_PROGRESS,
    DONE // 메서드가 추가될 경우 `;` 필요
}

public class Main {
    public static void main(String[] args) {
        OrderStatus status = Orderstatus.READY;
    }
}
  • 생성자 추가 시 접근 제한자는 private


2) 필요성

(1) Enum 미사용 시 문제점 (정수/문자열 상수 사용)
public static final int READY = 0;
int status = 0; // 999를 넣어도 컴파일 오류 발생하지 않음
  • 말도 안되는 값이 들어와도 컴파일러가 막지 못해 타입 안전성(type safety)이 없음
  • 값의 의미를 바로 알 수 없어 유지보수에 취약
  • 다른 그룹의 동일한 상수 값의 충돌로 로직 오류 발생
(2) Enum 사용 시 장점
  • 상수들이 하나의 타입으로 묶여, 타입 안전성(Type Safty)를 제공
  • IDE 자동완성 기능 극대화
  • 상수 이름만으로 의미가 명확해져, 코드 가독성이 높아짐


2-02. Enumswitch

1) 기존 if-else 기반 분기(문자열 비교) 방식의 문제점

문제점 설명
오타 위험 “ADNIM”처럼 오타가 나도 런타임 전까지 컴파일 오류를 알 수 없음
IDE 지원 약함 문자열에 대한 자동완성이 지원되지 않아 실수 쉬움
타입 안전성 부족 어떤 문자열이든 파라미터로 들어올 수 있어서 검증 로직이 복잡해짐
유지보수 어려움 값(문자열)이 변경될 경우 프로젝트 전체의 문자열을 수정해야 함


2) switch 기반 분기 방식의 장점

Enumswitch문과 결합했을 때 가독성이 가장 좋다. JDK 12 이상부터는 Switch Expression을 사용하여 더욱 간결히 표현할 수 있다.

장점 이유
컴파일러 보호 Enum 타입이 아닌 값이 들어오면 컴파일 오류 발생
오타 불가 IDE 자동완성으로 상수만 선택 가능
가독성 향상 조건이 명확하고 직관적
유지보수 용이 상수를 변경해도 IDE가 바로 안내


2-03. Enum 사용 시 주의사항(Anti-pattern 예방)

잘못 사용하면 유지보수 비용이 커지거나 런타임 오류 발생 가능한 문제

1) ordinal()은 비즈니스 로직에 사용하지 말 것

ordinal()은 상수가 선언된 순서를 정수로 반환

  • 문제점
    • 누군가 Enum 상수 순서를 변경하거나 새로운 상수를 중간에 추가하면 모든 비즈니스 로직과 저장된 데이터의 의미가 바뀜
    • 컴파일러가 아무 경고도 주지 않음
  • 해결책
    • 상수에 연결된 값은 ordinal() 메서드 대신 인스턴스 필드에 저장을 권고


2) DB 저장 시 name() 대신 고유 코드 사용

  • 문제점
    • 만약 Enum 이름을 변경하면? → DB에 저장된 값과 매핑 불가능
    • 과거 데이터에 대한 호환성 깨짐
  • 해결책
    • DB에는 name()이 아니라 개발자가 정의한 code 값을 저장하는 것이 안전 (예: “RD”, “IP”, “DN”)


3) Enum 상수 이름을 외부 입력값에 대한 방어적 설계

  • 문제점
    • 외부 API 요청으로 받은 문자열을 valueOf()로 직접 변환하면 대소문자가 불일치하거나 잘못된 값 입력 시 IllegalArgumentException 발생으로 서비스가 중단
  • 해결책
    • 안전한 변환 메서드 제공


2-04. Enum을 사용할 때 피해야 하는 반패턴(Anti-pattern)

Enum을 잘못 사용한 사례


1) 모든 Enum을 한 파일에 몰아넣기

  • 문제점
    • 책임 분리 실패
    • 파일이 지나치게 커짐
    • 협업 시 충돌 잦음
  • 해결책
    • Enum은 도메인별로 분리합니다.


2) Enum에 과도한 로직 삽입

  • 문제점
    • Enum 내부에서 DB 조회하거나 외부 서비스(API)를 직접 호출하는 것은 결합도를 높임
  • 해결책
    • Enum은 경량 구조여야 하며,
    • DB 호출, 외부 API 호출 등은 서비스 계층에서 처리해야 합니다.


3) Enum을 문자열처럼 사용하기

  • 문제점
    • String input = “READY”;
    • Status.valueOf(input) → 예외 발생 위험
  • 해결책
    • 문자열이나 정수 값을 Enum 으로 안전하게 변환해주는 메서드를 내부에 구현 (fromCode 등)

⭐⭐3. 예외 처리(Exception Handling)

3-01. 예외(Exception)의 개념과 필요성

자바에서 발생하는 문제 상황은 오류(Error)예외(Exception)으로 나뉨

  • 오류(Error) : 시스템 레벨에서의 심각한 문제로, 개발자가 복구할 수 없어 처리하지 않는 것이 일반적
  • 예외(Exception) : 프로그램 실행 중 발생할 수 있는 예상 가능한 문제로, 개발자가 적절히 처리
구분 Error Exception
의미 시스템 레벨에서 발생하는 치명적 문제 개발자가 처리할 수 있는 문제
예시 메모리 부족(OutOfMemoryError), 스택 오버플로(StackOverflowError) NullPointerException, IllegalArgumentException
복구 가능 여부 거의 불가능 대응 로직을 통해 비교적 복구 가능
처리 방법 JVM 차원 처리(프로그램 종료) 개발자가 try-catch 등으로 예외 상황 처리


1) 예외 처리의 중요성

파일이 존재하지 않거나, 네트워크 연결이 실패하거나, 사용자의 잘못된 입력, DB 연결 문제 등 외부 요인에 의해 발생하는 문제 상황은 개발자가 완벽히 막을 수 없다. 적절한 예외 처리가 없다면 프로그램은 즉시 종료됨

  • 예외 처리의 주요 역할
    • 프로그램 안정성 : 예기치 못한 종료 방지해 서비스 연속성 유지
    • 사용자 경험 향상 : 에러 코드에 대한 친절한 안내 메시지 제공
    • 에러 로깅 : 문제 원인을 기록하여 사후 분석
  • 예외 처리 시 원칙
    • 예외의 원인은 명확하게 기록
    • ⭐예외로 프로그램 흐름을 제어하지 않기
    • 예외 구조를 이해하면 나중에 Spring 예외 처리도 연결됨
    • Error는 절대 잡지 않기
    • 파일이나 네트워크 연결은 반드시 닫아야 함 (try-with-resources 활용)


2) 자바의 예외 계층 구조

  • Throwable : 자바에서 던질(throw) 수 있는 모든 객체의 최상위 부모
  • Error : 시스템 단에서 발생하는 치명적 오류이며 개발자가 처리하지 않음
  • Exception : 개발자가 처리 가능한 모든 예외의 부모 클래스
  • RuntimeException : 실행 중(Runtime) 자주 발생하는 예외들의 부모로, 주로 개발자의 논리적 실수에서 시작됨


⭐3) RuntimeException과 비 RuntimeException의 개념적 차이

구분 RuntimeException 그 외 Exception
발생 시점 실행 중(Runtime) 컴파일 단계에서 감지
처리 여부 선택적(Unchecked Exception) 반드시 처리 필요(Checked Exception)
대표 예외 NullPointerException, IllegalArgumentException IOException, SQLException


3-02. 체크드 예외 (Checked Exception)와 언체크드 예외 (Unchecked Exception)

1) 체크드 예외 (Checked Exception)

컴파일 시점에 반드시 처리해야 하는 예외로, 처리하지 않으면 컴파일 에러가 발생합니다. IDE에서 뜨는 빨간줄 중 일부는 체크드 예외 처리를 안 해서 생긴 컴파일 에러라고 볼 수 있다.

즉, 컴파일러가 “너 이 예외 발생할 수도 있는데 어떻게 처리할 거야?”라고 묻는 예외로, try-catch로 잡거나 throws로 던져야 한다.

대표 예시: IOException, SQLException, FileNotFoundException

  • 발생 이유: 외부 시스템(파일, 네트워크, DB 등)과의 상호작용에서 발생하는 불확실성
  • 대표 발생 주체: JVM 외부 시스템
  • 실무 활용: 안정성 보장을 위해 필요한 경우 사용
  • 특징: 프로그램 외부 환경에 의한 실패로, 실패를 복구할 방법이 있을 때 사용


2) 언체크드 예외(Unchecked Exception)

컴파일러가 처리 여부를 강제하지 않는 예외로, try-catch를 쓰지 않아도 컴파일되지만, 실행 중(Runtime)에 실패함

주로 RuntimeException을 상속받는 예외들이다.

대표 예시: NullPointerException, IllegalArgumentException, IndexOutOfBoundsException

  • 발생 이유: ⭐주로 개발자의 실수 또는 비즈니스 규칙 위반 시 발생
  • 대표 발생 주체: 코드 내부 논리
  • 실무 활용: 일반적으로 선호됨(가벼운 검증 실패, 잘못된 파라미터 전달, 프로그래밍 오류 표현)
  • 특징: 굳이 모든 곳에 예외 처리 코드를 적지 않아도 되기에 코드가 간결해짐

4. UML을 활용한 객체 지향 설계 시각화

4-01. UML(Unified Modeling Language)

시스템을 시각적으로 표현하기 위한 표준화된 모델링 언어로, 문서화나 기획할 때 필요한 도구

개발자에게는 “설계 지도”라고 이해하면 쉽다.


1) UML의 필요성

  • 복잡한 시스템의 시각적인 이해를 도움
  • 팀 간 소통 및 협업 향상에 도움
  • 설계 오류를 조기에 발견하는데 도움
  • 유지보수에 도움


2) UML의 목적

  • 구조 시각화 : 클래스, 객체, 패키지 등 시스템의 구성 요소를 표현
  • 동작 시각화 : 시퀀스, 상태, 활동 등 객체 간 상호작용을 표현
  • ⭐명세화(Specification) : 개발자 간 “이 기능은 이렇게 동작한다”라는 공통 이해를 위해 동작 및 구조를 명확히 정의
  • ⭐문서화(Documentation) : 시간이 지난 뒤에도 시스템의 구조를 파악할 수 있어 유지보수와 협업을 위한 설계 문서 역할을 함


4-02. UML의 종류

크게 구조 다이어그램행위 다이어그램으로 나뉨

1) 구조 다이어그램 (Static Diagram)

  • ⭐클래스 다이어그램
    • 객체 지향 시스템의 정적 구조를 시각적으로 표현하는 UML 다이어그램
    • 클래스(Class), 속성(Attribute), 메서드(Operation), 그리고 객체 간의 관계(Relationship)를 표현
  • 객체 다이어그램
  • 패키지 다이어그램
  • 컴포넌트 다이어그램

2) 행위 다이어그램 (Behavior Diagram)

  • ⭐시퀀스 다이어그램
    • 시간의 흐름에 따라 객체 간에 오가는 메시지(메서드 호출)를 표현하는 UML 다이어그램
    • 특정 기능이 어떤 순서로 진행되는지 파악할 때 사용
  • 활동(Activity) 다이어그램
  • 상태(State) 다이어그램


4-03. UML 작성 툴

도구 방식 설치 여부 협업 장점 단점
Mermaid 코드 기반 불필요 우수 (Notion, GitHub 지원) 빠른 작성, 버전 관리 쉬움 복잡한 다이어그램은 문법 난도 ↑
Draw.io 드래그&드롭 GUI 불필요 보통 직관적, 시각화 강력 버전 관리 어려움
PlantUML 코드 기반 필요 (or 서버) 우수 복잡한 다이어그램 표현력 최고 설정 난이도 ↑

Leave a comment