Java - switch to interface

조건분기: 미궁처럼 복잡한 분기 처리를 무너뜨리는 방법

Posted by Yan on December 20, 2023

조건 분기가 중첩되어 낮아지는 가독성 개선하는 방법

  1. 조기 리턴으로 중첩 제거하기
  2. 가독성을 낮추는 else 구문도 조기 리턴으로 해결하기

switch 조건문

switch 조건문 중복이 위험한 이유

  1. 같은 형태의 switch 조건문이 여러 개 사용될 수 잇다.
  2. 요구사항 변경 시 수정(case문 추가)을 누락할 수 있다.
  3. switch조건문이 폭발적으로 늘어날 수 있다.

switch 조건문 중복을 해소하는 방법

단일 책임 선택의 원칙에 대해 생각해보아야 한다.

소프트웨어 시스템이 선택지를 제공해야 한다면, 그 시스템 내부의 어떤 한 모듈 만으로 모든 선택지를 파악할 수 있어야 한다.

-> 조건 식이 같은 조건 분기를 여러 번 작성하지 말고 한 번에 작성하자는 뜻이다.

먼저 switch 조건문을 하나로 묶은 후 인터페이스를 사용하면 된다.

인터페이스 적용 예시

도형의 나타내는 클래스 Rectangle과 Circle이 있다고 한다면, 면적을 구하는 area메소드가 이름이 같아도 서로 다르다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rectangle {
  private final double width;
  private final double height;
  double area() {
    return width * height;
  }
}

class Circle {
  private final double radius;
  double area() {
    return radius * radius * Math.PI;
  }
}

이러한 경우 도형(Object shape)의 area를 구할 때마다 Circle 인지, Rectangle인지 instanceof를 체크해야되는 분기조건이 필요할 수 있다.

이럴 때 도형이라는 interface를 만들어주면 Circle과 Rectangle같은 다른 자료형을 같은 자료형처럼 사용할 수 있게 된다.

1
2
3
interface Shape {
  double area();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rectangle implements Shape {
  private final double width;
  private final double height;
  double area() {
    return width * height;
  }
}

class Circle implements Shape {
  private final double radius;
  double area() {
    return radius * radius * Math.PI;
  }
}

전략패턴으로 인터페이스를 switch 조건문 중복에 응용하기

  • 종류별로 다르게 처리해야 하는 기능을 인터페이스의 메서드로 정의하기
  • 인터페이스의 이름을 결정하는 방법 : ‘어떤 부류에 속하는지’ 생각해 볼 것
  • 종류별로 클래스를 만들고 각각의 클래스에 인터페이스를 구현할 것
  • Switch 조건문에 의존하지 않고 다른 형태로 전환하기 어려울 때, Map으로 변경해보기. type을 Enum으로 지정하고 값을 인터페이스 구현 클래스의 인스턴스로 지정해보자.
  • 메서드를 구현하지 않으면 오류로 인식하게 만들기

Map이 switch 조건문처럼 경우에 따라 처리를 구분하는 방법

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
31
32
33
34
35
36
37
38
final Map<MagicType, Magic> magics = new HashMap<>();

final Fire fire = new Fire(member);
final Lightning lightning = new Lightning(member);
final HellFire hellfire = new HellFire(member);

magics.put(MagicType.fire, fire);
magics.put(MagicType.lightning, lightning);
magics.put(MagicType.hellFire, hellFire);

void magicAttack(final MagicType magicType) {
  final Magic usingMagic = magics.get(magicType);

  showMagicName(usingMagic);
  consumeMagicPoint(usingMagic);
  consumeTechnicalPoing(usingMagic);
  magicDamage(usingMagic);
}

void showMagicName(final Magic magic) {
  final String name = magic.name();
  //생략
}

void comsumeMagicPoint(final Magic magic) {
  final int costMagicPoint = magic.costMagicPoint();
  //생략
}

void consumeTechnicalPoint(final Magic magic) {
  final int costTechnicalPoint = magic.costTechnicalPoint();
  //생략
}

void magicDamage(final Magic magic) {
  final int attackPower = magic.attackPower();
  //생략
}
  • magics.get(magicType)으로 모두 한꺼번에 전환. 처리별로 switch 문을 사용하는 분기 자체를 하지 않음
  • 전략 패턴: 인터페이스를 사용해서 처리를 한꺼번에 전환하는 설계
reference

내 코드가 그렇게 이상한가요?