item 23
태그 달린 클래스보다는 클래스 계층구조를 활용하라
01. 태그 달린 클래스
태그 : 클래스가 어떤 타입인지에 대한 정보를 담고있는 멤버 변수
태그 달린 클래스는 두 가지 이상의 의미를 가질 수 있다.
현재 표현하는 의미를 태그 값으로 알려주는 클래스를 태그 달린 클래스라고 한다.
아래 BoardGame 클래스를 보면 세 가지 필드가 존재한다.
gameType, cards, board
이때 BoardGame이 어떤 타입인지에 대한 정보를 갖는 "GameType"을 태그라고 한다.
public class BoardGame {
enum GameType {
BLACKJACK,
CHESS
}
// 태그, 사람이 어떤 게임을 갖고 있는지
private GameType gameType;
// 블랙잭 게임일 경우 사용
private List<String> cards = new ArrayList<>();
// 체스 게임일 경우 사용
private String[][] board = new String[8][8];
public String findWinner() {
if (gameType == GameType.BLACKJACK) {
// 블랙잭 게임 로직
return "블랙잭 게임 승리자 이름";
}
// 체스 게임 로직
return "체스 게임 승리자 이름";
}
}
02. 태그 달린 클래스는 되도록 사용하지 말자
태그 달린 클래스는 비효율적이다.
BoardGame에서 GameType에 따라서 사용하지 않는 필드가 존재있기 때문이다.
GameType이 GameType.BLACKJACK일 경우 String[][] board 필드는 사용하지 않는다.
public class BoardGame {
enum GameType {
BLACKJACK,
CHESS
}
// 태그, 사람이 어떤 게임을 갖고 있는지
private GameType gameType = GameType.BLACKJACK;
// [O] 블랙잭 게임일 경우 사용
private List<String> cards = new ArrayList<>();
// [X] 체스 게임일 경우 사용
private String[][] board = new String[8][8]; // [사용하지 않는 필드]
public String findWinner() {
if (gameType == GameType.BLACKJACK) {
// [O] 블랙잭 게임 로직
return "블랙잭 게임 승리자 이름";
}
// [X] 체스 게임 로직
return "체스 게임 승리자 이름";
}
}
마찬가지로 GameType이 GameType.Chess일 경우 List<String> cards 필드는 사용하지 않는다.
public class BoardGame {
enum GameType {
BLACKJACK,
CHESS
}
// 태그, 사람이 어떤 게임을 갖고 있는지
private GameType gameType = GameType.CHESS;
// [X] 블랙잭 게임일 경우 사용
private List<String> cards = new ArrayList<>(); // [사용하지 않는 필드]
// [O] 체스 게임일 경우 사용
private String[][] board = new String[8][8];
public String findWinner() {
if (gameType == GameType.BLACKJACK) {
// [X] 블랙잭 게임 로직
return "블랙잭 게임 승리자 이름";
}
// [O] 체스 게임 로직
return "체스 게임 승리자 이름";
}
}
이와 같이 태그 달린 클래스를 사용할 경우 사용하지 않는 불필요한 필드가 존재하게 된다.
또한 단일 책임 원칙(SRP: Single Responsibility Principle)를 위반하기도 한다.
단일 책임 원칙을 위반하는 이유는
- BoardGame에서 "BLACKJACK"의 책임과 "CHESS"의 책임 두 가지를 갖기 때문이다.
03. 태그 달린 클래스 대신 클래스 계층 구조를 사용하자.
태그 달린 클래스에서의 문제점을 해결하기 위해서는 클래스 계층 구조를 사용하면 된다.
클래스 계층 구조를 이용하기 위해서는
- 현재 공통된 부분을 상위 클래스에 올리고
- 변경된 부분을 하위 클래스에서 재정의하면 된다.
BoardGame을 인터페이스 변경했다.
public interface BoardGame {
String findWinner();
}
Chess와 BlackJack은 BoradGame을 구현하고 따로 필드를 갖도록 구현했다.
public class BlackJack implements BoardGame {
private List<String> cards = new ArrayList<>();
@Override
public String findWinner() {
// ...
return "블랙잭 게임 승리자 이름";
}
}
public class Chess implements BoardGame {
private String[][] board = new String[8][8];
@Override
public String findWinner() {
// ...
return "체스 게임 승리자 이름";
}
}
클래스 계층 구조를 이용하면서 단일 책임 원칙 위반을 하지 않게 되었다.
BlackJack 클래스는 블랙잭 게임에 관련된 책임만을 가지고 있다.
Chess 클래스는 체스 게임에 관련된 책임만을 가지고 있다.
태그 달린 클래스 대신 클래스 계층 구조를 이용하니 보다 유연한 코드가 작성됨을 확인했다.
👊 정리하자면
태그 달린 클래스를 이용하면 비효율적이고 SOLID 원칙에 위반할 수 있다.
이러한 문제점을 해결하기 위해서 클래스 계층 구조를 이용하는 방법을 생각해보자.
'💬 언어 > 이펙티브 자바' 카테고리의 다른 글
[이펙티브 자바] Item 22 - 인터페이스 타입을 정의하는 용도로만 사용하라 (1) | 2023.03.20 |
---|---|
[이펙티브 자바] Item 20 - 추상 클래스보다는 인터페이스를 우선하라 (0) | 2023.03.13 |
[이펙티브 자바] Item 49 - 매개변수가 유효한지 검사하라 (0) | 2023.03.06 |
[이펙티브 자바] Item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.03.05 |
[이펙티브 자바] Item 26 - Raw Type은 사용하지 말라 (0) | 2023.02.22 |