출처: https://blog.iroot.kr/341 [RootKR] 출처: https://blog.iroot.kr/341 [RootKR]

 게임에서 보면 캐릭터 종족별로 공격력이나 방어력이 제각각이다. 

 

이런 캐릭터 클래스를 절차 지향부터 어떻게 전략 패턴을 활용하는지 살펴보자

 

게임상 캐릭터 종족에따라 방어력이 달라지는 코드를 예를 들어보면 다음처럼 if문을 통하면 될 거 같다.

 

if 문으로 방어력 구분

public class Character {
    private int attack;
    private int defense;
    private String type;

    public int defense() {
        if(type.equals("오크")) {
            this.defense = 100;
        } else if(type.equals("휴먼")) {
            this.defense = 50;
        } else if(type.equals("트롤")) {
            this.defense = 40;
        }
        return defense;
    }
}

 위 코드의 문제는 종족이 추가 될 때마다 if문 코드를 수정 삭제해야 한다는 문제가 있다. 그리고 코드가 절차 지향적이다. 문제가 있어 보여 캐릭터별 클래스를 만들면 어떨까 해서 적용해 봤다. 

 

개별적 클래스로 만들어 직접 의존하기

public class Ork {
    private int attack;
    private int defense;
    
    public int defense() {
        this.defense = 100;
        return defense;
    }
}

public class Human {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 50;
        return defense;
    }
}

public class Trol {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 40;
        return defense;
    }
}

이에 따른 Human 클래스는 다음과 같다.

public int defense() {
        if(type.equals("오크")) {
            Ork ork = new Ork();
            this.defense = ork.defense();
        } else if(type.equals("휴먼")) {
            Human human = new Human();
            this.defense = human.defense();
        } else if(type.equals("트롤")) {
            Trol trol = new Trol();
            this.defense = trol.defense();
        }
        return defense;
    }

 뭔가 코드가 바뀐거 같지만, 여전히 if문이 난무하고 의존을 클라이언트(Character)에서 직접 하고 있다. 여전히 종족이 추가되면 Character 클래스의 수정이 필수적이다. 

 

 아.. 어떻게 하지... 상속이란것도 배웠는데...!!

 

상속을 썻지만 직접 의존

 다형성의 객체지향 개념으로 상속이란 개념을 적용해 보는 코드를 살펴보자. 

// 종족 추상클래스
public abstract class Tribe {
    private int attack;
    private int defense;

    abstract int defense();
}

public class Ork extends Tribe {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 100;
        return defense;
    }
}

public class Human extends Tribe {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 50;
        return defense;
    }
}

public class Trol extends Tribe {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 40;
        return defense;
    }
}

Character 클래스는 다음과 같이 변경된다. 

public int defense() {
        Tribe tribe = null;
        if(type.equals("오크")) {
            tribe = new Ork();
        } else if(type.equals("휴먼")) {
            tribe = new Human();
        } else if(type.equals("트롤")) {
            tribe = new Trol();
        }
        this.defense = tribe.defense();
        
        return defense;
    }

 뭔가 다형성까지 적용해 객체지향스러움을 자랑하고 싶지만 여전히 if문은 제거하지 못했다.... 이제 Stratey 패턴이 등장할 때가 왔다. 

 

 스트래태지 패턴의 원칙인 바뀌는 부분을 찾아 바뀌지 않는 것으로부터 분리시켜 캡슐화하는 것부터 해보자. 여기서 캡슐화란 객체가 해야 할 기능 중 일부를 별도의 그룹으로 뽑아서 캡슐화한다는 뜻이다. 내가 사용하는  스프링 IoC에서 스트레테지 패턴을 편하게 해 준다.

 변하는 부분은 Character 클래스에서 변하는 부분은 종족에 따른 방어력 부분이다. 이를 캡슐화시켜보자. 방어력 부분을 인터페이스로 만들고 이를 구현한 구체 클래스를 만들고 Character 클래스에서 이를 집약시키면 된다. 

public interface CharacterDefense {
    int defense();
}

public class Ork implements CharacterDefense {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 100;
        return defense;
    }
}

public class Human implements CharacterDefense {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 50;
        return defense;
    }
}

public class Trol implements CharacterDefense {
    private int attack;
    private int defense;

    public int defense() {
        this.defense = 40;
        return defense;
    }
}

 위 코드에 따른 Character 코드는 다음과 같다. 

public class Character {
    private int attack;
    private CharacterDefense characterDefense;

    public void setCharacterDefense(CharacterDefense characterDefense) {
        this.characterDefense = characterDefense;
    }

    public int defense() {
        return characterDefense.defense();
    }
}

 와우... 수많은 if문이 사라졌고, Character는 CharaterDefense의 구체적인 부분을 몰라도 된다. 즉 의존성도 확 줄었다. 종족이 추가된다면 Character 클래스는 바뀔 필요가 없어졌다. OCP도 만족한다. 

 

전략 패턴의 의도

 동일 계열의 알고리즘군(CharacterDefense)을 정의하고, 각 알고리즘을 캡슐화하여 이를 상호작용할 수 있게 만든다. 

 

활용성

 다음 상황에서 전략 패턴을 활용할 수 있다. 

  • 행동들이 조금씩 다를 뿐, 개념적으로 관련된 많은 클래스들이 존재할 때. 전략 패턴은 많은 행동 중 하나를 가진 클래스를 구성할 수 있는 방법을 제공한다.
  • 알고리즘의 변형이 필요할 때
  • 사용자가 몰라야 하는 데이터를 사용하는 알고리즘이 있을 때.
  • 하나의 클래스가 수많은 행동을 정의하고, 이런 행동들이 클래스 안에서 수많은 분기분들로 이뤄졌을 때.

 

 전략 패턴은 실전에서도 많이 사용된다. 나 같은 경우에도 전 회사에서 상품 주제별 상품 리스트를 보여주는 서비스가 있었는데, 전략 패턴으로 많은 이득을 보았다. 그럼 이것으로 마무리를 지으려 한다.

 

위 코드는 하단에서 확인할 수 있다. 

https://github.com/jinioh88/design_pattern/tree/master/src/main/java/com/study/design_pattern/strategy 

+ Recent posts