Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
Tags
- 0.25px border
- Props
- 데이터베이스 #try #이중
- 타입스크립트
- ZOOM
- 컴포넌튼
- font-size
- Strict
- entity
- Websocket
- TS
- 1px border
- &연산
- 전역변수
- angular
- 문서번호
- 0.5px border
- 당근마켓
- es6
- TypeScript
- 으
- literal
- 서버리스 #
- jwt
- 0.75px border
- ES5
- github
- 클론코딩
- npm
- 10px
Archives
- Today
- Total
복잡한뇌구조마냥
[JAVA] Effective Java - 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (23) 본문
요약
- “태그 달린 클래스(tagged class)”는 한 클래스 안에 kind 같은 태그값으로 여러 형태를 구분하는 방식.
- 이 방식은 필드/메서드가 뒤섞여 지저분해지고, 생성자·검증·equals/hashCode 같은 공통 로직이 복잡해지며, 실수가 생기기 쉽다.
- 추상 클래스/인터페이스 기반 계층구조로 바꾸면 타입별 필드만 갖게 되고, 컴파일러가 누락/오류를 잡아주며, 가독성과 유지보수성이 크게 좋아진다.
- 현대 자바(17+)라면 sealed 클래스/인터페이스로 더 안전하게 닫힌 계층을 만들 수 있다.
1) 태그 달린 클래스가 뭔가요?
public class Shape {
public enum Kind { CIRCLE, RECTANGLE }
private final Kind kind;
// CIRCLE 전용
private final double radius;
// RECTANGLE 전용
private final double width;
private final double height;
public Shape(double radius) {
this.kind = Kind.CIRCLE;
this.radius = radius;
this.width = 0;
this.height = 0;
}
public Shape(double width, double height) {
this.kind = Kind.RECTANGLE;
this.radius = 0;
this.width = width;
this.height = height;
}
public double area() {
switch (kind) {
case CIRCLE: return Math.PI * radius * radius;
case RECTANGLE: return width * height;
default: throw new AssertionError(kind);
}
}
}
문제점
- 지저분한 필드: 한 형태에만 쓰는 필드가 공존 → 미사용 필드/더미 값 발생.
- 생성자·검증 폭탄: 태그별로 다른 불변식(예: 원은 radius > 0, 직사각형은 width, height > 0)을 한 클래스에서 모두 처리 → 누락/버그 위험.
- 조건 분기 남발: switch(kind)가 이곳저곳에 퍼짐 → 새 형태 추가 시 전부 수정해야 함.
- 타입 안전성 부족: 논리적으로 불가능한 상태(예: kind=CIRCLE인데 width 접근)를 컴파일 타임에 막기 어려움.
- equals/hashCode/toString의 취약성: 태그마다 다르게 계산해야 해 코드가 길어지고 실수하기 쉽다.
2) 클래스 계층구조로 바꾸기
public abstract class Shape {
public abstract double area();
}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
if (radius <= 0) throw new IllegalArgumentException("radius > 0");
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public final class Rectangle extends Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
if (width <= 0 || height <= 0) throw new IllegalArgumentException("width,height > 0");
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
장점
- 필드 정합성: 각 타입은 자기에게 필요한 필드만 가짐 → 불필요·미사용 필드 사라짐.
- 불변식 캡슐화: 유효성 검사가 각 생성자에 자연스럽게 위치 → 누락 가능성↓.
- 조건 분기 제거: 다형성으로 switch(kind) 사라짐 → 새 타입 추가도 폐쇄적 수정.
- 타입 안전성 향상: 컴파일러가 메서드/필드 사용을 체크.
- 가독성과 테스트 용이성: 각 타입이 작고 응집도 높아짐.
3) 현대 자바라면: sealed(자바 17+)
닫힌 계층을 만들고 싶다면 sealed로 확장 가능 타입을 통제할 수 있다.
public sealed abstract class Shape permits Circle, Rectangle {
public abstract double area();
}
public final class Circle extends Shape { /* ... */ }
public final class Rectangle extends Shape { /* ... */ }
- 이외 타입이 임의로 상속할 수 없음 → 모델의 완결성/안전성 보장.
- switch 표현식과 함께 쓰면 총합 타이핑(algebraic data type) 느낌으로 안전하게 패턴 매칭 가능(미래의 패턴 매칭 for switch와 궁합 좋음).
4) 언제 태그 달린 클래스가 “그나마” 괜찮을까?
- 아주 작고 임시인 데이터 구조, 또는 JVM 간 직렬화 포맷 호환을 위한 얇은 DTO처럼 변형이 거의 없고 규칙이 단순한 경우.
- 그래도 가능하면 enum + 분기 대신 작은 타입 분리를 검토하는 게 장기적으로 안전하다.
5) 실전 적용 팁(리팩터링 체크리스트)
- 클래스 안에 서로 다른 의미의 필드 묶음이 공존하는가?
- 메서드 곳곳에 if/switch (kind)가 반복되는가?
- 생성자/유효성 검증이 태그값에 따라 분기하는가?
- 새 “종류”를 추가할 때 여러 파일을 돌아다니며 수정하는가?
→ 그렇다면 추상 타입 + 하위 타입으로 쪼개자. 필요하면 sealed로 닫자.
6) 한 줄 결론
“태그로 형태를 구분하는 한 덩어리 클래스를 만들지 말고, 다형성이 드러나는 클래스 계층으로 쪼개라.
그러면 필드·불변식·행동이 자연스럽게 자기 자리로 돌아온다.”
LIST
'BE > JAVA' 카테고리의 다른 글
[JAVA] 리플렉션(Reflection) 기초 (0) | 2025.08.13 |
---|---|
[JAVA] Effective Java - 로 타입(raw type)은 사용하지 말라 (26) (2) | 2025.08.11 |
[JAVA] 객체 지향 설계의 5대 원칙 – SOLID (3) | 2025.08.10 |
[JAVA] 서블릿(Servlet) (0) | 2025.08.08 |
[JAVA] JSP ( Java Server Pages ) (0) | 2025.08.07 |