컬렉션 : 중첩을 제거하는 구조화 테크닉

hyyyynjn·2024년 4월 15일
0

컬렉션 : 중첩을 제거하는 구조화 테크닉

배열과 List와 같은 컬렉션에 관련된 테크닉을 알아보자


7.1 이미 존재하는 기능을 다시 구현하지 말기

컬렉션 요소들 가운데 조건을 만족하는지 확인하는 방법

  • for loop을 돌면서 if 조건문을 통해 리스트 내부 요소에 접근하여 확인 (가독성 bad)
  • stream 표준 컬렉션 라이브러리 anyMatch 활용하기 (가독성 good)

바퀴의 재발명을 방지하기 위해 미리 프레임워크의 기능과 라이브러리를 잘 확인하자.

  • 바퀴의 재발명 : 이미 널리 사용중인 기술을 모르거나 무시하여 비슷한 것을 새로 만들어내는 것
  • 네모난 바퀴의 재발명 : 이미 존재하는 것보다 좋지 못한 결과물을 만들어 내는 것


7.2 반복 처리 내부의 조건 분기 중첩

컬렉션 요소 중 특정 조건을 만족하는 요소에 대해서만 작업을 수행하고자 할 경우.

continue로 가독성 높이기

  1. for loop + if 조건문 + if 조건문 중첩 : 가독성 똥망
  2. for loop + if 조건문 + continue : continue로 중첩 조건문을 제거할 수 있음
// for loop + if 조건문 + if 조건문 중첩
for (Member member : members) {
  if (0 < member.hitPoint) {
    if (member.containsState(StateType.poison) {
      member.hitPoint -= 10;
      if (member.hitPoint <= 0) {
        member.hitPoint = 0;
        member.addState(StateType.dead);
        member.removeState(StateType.poison);
      }
    }
  }
}

// for loop + if 조건문 + continue
for (Member member : members) {
  if (member.hitPoint == 0) {
    continue;
  }

  if (member.containsState(StateType.poison) {
    continue;
  }
  
  member.hitPoint -= 10;
  
  if (member.hitPoint <= 0) {
    continue;
  }

  member.hitPoint = 0;
  member.addState(StateType.dead);
  member.removeState(StateType.poison);
}

break로 가독성 높이기

  1. for loop + if 조건문 + else break : 가독성 똥망
  2. for loop + if 반전 조건문 + break : early-return 처럼 for loop에서 조건을 반전하고 break.
// for loop + if 조건문 + else break
int totalDamage = 0;
for (Member member : members) {
  if (member.hasTeamAttackSucceeded()) {
    int damage = (int) (member.attack * 1.1);
    
    if (30 <= damage) {
      totalDamage += damage;
    } else {
      break;
    }
  } else {
    break;
  }
}

// for loop + if 반전 조건문 + break
int totalDamage = 0;
for (Member member : members) {
  if (!member.hasTeamAttackSucceeded()) {
    break;
  }

  int damage = (int) (member.attack * 1.1);
  
  if (30 > damage) {
    break;
  }

  totalDamage += damage;
}


7.3 응집도가 낮은 컬렉션 처리

컬렉션에 요소를 추가할 경우, 데이터(컬렉션)와 기능(추가)이 분리되면 응집도가 낮아진다.

응집도가 낮은 예시

class FieldManager {
  void addMember(List<Member> members, Member newMember) {
    if (유효성_검사_1) {
      throw new Exception("error1");
    }
    if (유효성_검사_2) {
      throw new Exception("error2");
    }

    members.add(newMember);
  }

  boolean partyIsAlive(List<Member> members) {
    return members.stream().anyMatch(Member::isAlive);
  }
}

class SpecialEventManager {
  // FieldManager#addMember 에서 유효성 검사 로직 누락
  void addMember(List<Member> members, Member newMember) {
    members.add(newMember);
  }
}

class BattleManager {
  // FieldManager#partyIsAlive 와 동일한 기능
  boolean membersAreAlive(List<Member> members) { 
    for (Member member : members) {
      if (member.isAlive()) return true;
    }
    return false;
  }
}

List 라는 데이터에 대해 요소 추가, 요소 중 조건 체크 와 관련된 기능이 분리되어 있기 때문에 응집도가 매우 낮아지게 된다

  • 서로 다른 클래스에서 같은 기능을 중복 구현
  • 기능 수행 이전에 유효성 검사 로직 누락

7.3.1 컬렉션 처리를 캡슐화하기 : 일급 컬랙션

일급 컬렉션 : 컬렉션과 관련된 로직을 캡슐화하는 디자인 패턴

  • 컬렉션 자료형의 인스턴스 변수
  • 컬렉션 자료형의 인스턴스 변수에 잘못된 값이 할당되지 않게 막고, 정상적으로 조작하는 메소드
class Party {
  private final List<Member> members;

  Party() {
    members = new ArrayList<>();
  }

  // members의 요소가 변경되는 부수효과를 방지한 add 메소드
  Party add(final Member member) {
    if (유효성_검사_1) {
      throw new Exception("error1");
    }
    if (유효성_검사_2) {
      throw new Exception("error2");
    }

    List<Member> adding = new ArrayList<>(members);
    adding.add(member);
    return new Party(adding);
  }

  boolean isAlive() {
    return members.stream().anyMatch(Member::isAlive);
  }
}

7.3.2 외부로 전달할 때 컬렉션의 변경 막기

일급 컬렉션의 인스턴스 변수를 외부로 그대로 노출하면, 인스턴스 변수에 요소를 추가하고 제거하여 부수효과가 발생한다.

  • 불변 컬렉션 형태로 반환하여 외부에서 조작하지 못하도록 해라. (unmodifiableList)
class Party {
  private final List<Member> members;

  List<Member> members() {
    return members.unmodifiableList();
  }
}

0개의 댓글