이미 구현되어 있는 고차함수(map, filter, findIndex 등)을 사용해서 복잡해지는 로직을 직접 구현하지 않고도 가독성을 높일 수 있다.
// bad
let hasPrisonKey = false;
for (let i = 0; i <= items.length; i++) {
if (items[i].name === '감옥 열쇠') {
hasPrisonKey = true;
break;
}
}
// good
const hasPrisonKey = items.findIndex((item) => item.name === '감옥 열쇠') !== -1;
// some 함수로도 가능
for (let i = 0; i <= members.length; i++) {
const member = members[i];
if (members.hitPint === 0) continue;
if (!members.containsState(StateType.poison)) continue;
member.hitPoint -= 10;
if (member.hitPoint > 0) continue;
member.hitPoint = 0;
member.addState(StateType.dead);
member.removeState(StateType.poison);
}
}
중첩된 if문을 사용하는 대신에 continue로 건너뛰게 해준다.
let totalDamage = 0;
for (let i = 0; i <= members.length; i++) {
const member = members[i];
if (!member.hasTeamAttackSucceeded()) break;
let damage = Math.floor(member.attack() * 1.1);
if (damage < 30) break;
totalDamage += damage;
}
}
마찬가지로 중첩된 if문을 사용하는 대신에 break로 가독성을 높인다.
프론트의 자료형에는 컬렉션이 없기에, 배열, set 으로 보면 될 것 같다.
class FieldManager {
// 멤버를 추가한다.
addMember(members: Array<Member>, newMember: Member) {
if (members.findIndex((member) => member.id === newMember.id) !== -1) {
throw new Error('이미 존재하는 멤버입니다.');
}
if (members.size() === MAX_MEMBER_COUNT) {
throw new Error('이 이상 멤버를 추가할 수 없습니다.');
}
members.push(newMember);
}
// 파티 멤버가 1명이라도 존재하면 true를 리턴
partyIsAlive(members: Array<Member>) {
return members.filter((member) => member.isAlive()).length > 0;
}
}
게임에서 필드 맵을 관리하는 클래스라고 할 때, 게임 내에서 멤버를 추가하는 시점이 있을 수 도 있다.
class SpecialEventManager {
addMember(members: Array<Member>, member: Member) {
members.push(member);
}
}
멤버를 추가하기 위한 메서드가 하나 더 생성되어 중복된 코드가 만들어졌다. 즉, members관련된 로직을 처리하기 위해 코드가 여기저기에 구현되어 응집도가 낮아진 것이다.
일급 컬렉션 패턴을 사용해 해결할 수 있고, 일급 컬렉션이란 컬렉션과 관련된 로직을 캡슐화 하는 디자인 패턴이다.
클래스에 있어야 할 두가지를 기본적으로 따라야 한다.
일급 컬렉션은
member 컬렉션을 인스턴스 변수로 가지는 클래스를 다음과 같이 설계할 수 있다.
type Member= {
id: number
isAlive: () => boolean
}
class Party {
static readonly MAX_MEMBER_COUNT = 4;
private readonly members: Array<Member>
constructor(members?: Array<Member>){
if(members) {
this.members = members
} else this.members = new Array<Member>();
}
add(newMember: Member) {
if(this.exists(newMember)) {
throw new Error('이미 파티에 참가되어 있습니다.');
}
if(this.isFull()) {
throw new Error('이 이상 멤버를 추가할 수 없습니다.');
}
const adding = [...this.members];
adding.push(newMember);
return new Party(adding);
}
isAlive() {
return this.members.some((member) => member.isAlive());
}
exists(member:Member) {
return this.members.some((member) => member.id === member.id);
}
isFull() {
return this.members.length === Party.MAX_MEMBER_COUNT;
}
}
컬렉션을 조작하고 확인하는 로직을 한곳으로 모아 응집도를 높였다.
외부에서 멤버 인스턴스를 참조하려고 할 때 그대로 외부로 전달하면 추가와 제거가 가능해진다.
이를 막기 위해 unmodifiableList 메서드를 사용한다. (자바스크립트에는 존재하지 않는 메서드이므로 다른 방법으로 설명하겠다.)
class Party {
// 생략
members() {
return this.members;
}
}
여러가지 방법이 있어보이는데
방법1. 일단 원본을 복사해서 리턴하기
class Party {
// 생략
members() {
return [...this.members];
}
}
방법2. getter와 setter 사용
class Party {
// 생략
get getMembers() {
return [...this.members];
}
set setMembers() {
}
}
위 방법은 모두 원본을 복사해서 넘기기에 원본은 변하지 않지만 넘어가서 사용할때는 변경이 가능하다.
방법3. Object.freeze 사용하기
class Party {
// 생략
getMembers() {
return Object.freeze(this.members);
}
}
Object.freeze를 쓰면 객체를 조작하는게 불가능해지기에 조작 불가능하게 된다.