참고: 최범균님의 개발자가 반드시 정복해야 할 객체지향과 디자인 패턴

디자인 패턴이란?
디자인 패턴이란 객체지향적 설계를 하면서 자주 나오는 설계기법(클래스, 객체의 구성, 객체 간 메시지 흐름)을 정리해서 모아놓은 것.

전략 패턴이란?
전략 패턴: RunTime동안 알맞는 알고리즘을 다이내믹하게 선택해서 사용하는 것. (여기서 말하는 알고리즘은 코딩테스트할 때 나오는 알고리즘이 아니라 객체의 행위라 보면 좋겠다.)

아래 예시를 보면서 이해하자!


//전략패턴 사용하기 전
public class Main {
    public static void main(String[] args) {
        int points = 10;
        Calculator calculator = new Calculator();
        calculator.calculate("son", points);
    }

    public static class Calculator {
        public Calculator() {
        }

        public int calculate(String relationStatus, int points) {
            if (relationStatus.equals("son")) {
                return points * 100;
            }
            else if (relationStatus.equals("mom")) {
                return points * 100 + 20;
            }
            else if (relationStatus.equals("dad")) {
                return points * 200;
            }
            throw new IllegalArgumentException();
        }
    }
}

위에 예시처럼 코드를 짠다면 변경과 확장에 취약한 코드가 된다. 새로운 relationStatus가 생길 때 마다 if-문이 늘어난다. 즉 확장이 일어날 때마다 기존의 코드를 바꾸어야 한다. 이러한 코드는 매번 바뀌는 상황에 대처를 못하는 코드이다. 또한, if-문이 계속 늘어난다면 가독성이 떨어지는 코드가 완성된다. 이제 전략패턴을 적용한 코드를 보자.

//전략패턴 step1
public class Main {
    public static void main(String[] args) {
        int points = 10;
        String relationStatus = "son";
        CalculateStrategy strategy = null;
        Calculator calculator = new Calculator();

        if (relationStatus.equals("son")) {
            strategy = new SonCalculateStrategy();
        }
        else if (relationStatus.equals("mom")) {
            strategy = new MomalculateStrategy();
        }
        else if (relationStatus.equals("dad")) {
            strategy = new DadCalculateStrategy();
        }

        calculator.calculate(strategy, points);
    }

    public static class Calculator {
        public Calculator() {
        }

        public int calculate(CalculateStrategy strategy, int points) {
            return strategy.calculate(points);
        }
    }

    public interface CalculateStrategy {
        int calculate(int points);
    }

    public static class SonCalculateStrategy implements CalculateStrategy {
        @Override
        public int calculate(int points) {
            return points*100;
        }
    }

    public static class MomalculateStrategy implements CalculateStrategy {
        @Override
        public int calculate(int points) {
            return points * 100 + 20;
        }
    }

    public static class DadCalculateStrategy implements CalculateStrategy {
        @Override
        public int calculate(int points) {
            return points * 200;
        }
    }
}

//우선 계산하는 "행위"를 객체로 빼놓아서 확장이 쉽게 만들었다.
//하지만 아직 main에 있는 if-문들이 마음에 걸린다. step2로 넘어가자!
//step 2
//Main에 어지럽게 박혀있던 if,else문들을 getStrategy함수로 정리했다.
public class Main {
    private static List<CalculateStrategy> strategies = Arrays.asList(new SonCalculateStrategy(), new MomCalculateStrategy(), new DadCalculateStrategy());

    public static void main(String[] args) {
        int points = 10;
        String relationStatus = "son";
        Calculator calculator = new Calculator();
        CalculateStrategy strategy = getStrategy(relationStatus);
        calculator.calculate(strategy, points);
    }

    private static CalculateStrategy getStrategy(String relationStatus) {
        return strategies.stream()
                .filter(strategy -> strategy.isCalculable(relationStatus))
                .findAny()
                .orElseThrow(IllegalArgumentException::new);
    }

    public static class Calculator {
        public Calculator() {
        }

        public int calculate(CalculateStrategy strategy, int points) {
            return strategy.calculate(points);
        }
    }

    public interface CalculateStrategy {
        boolean isCalculable(String relationStatus);
      //isCalculable를 사용함으로써 응집도를 높혔다.
        int calculate(int points);
    }

    public static class SonCalculateStrategy implements CalculateStrategy {
        @Override
        public boolean isCalculable(String relationStatus) {
            return relationStatus.equals("son");
        }

        @Override
        public int calculate(int points) {
            return points*100;
        }
    }

    public static class MomCalculateStrategy implements CalculateStrategy {
        @Override
        public boolean isCalculable(String relationStatus) {
            return relationStatus.equals("mom");
        }

        @Override
        public int calculate(int points) {
            return points * 100 + 20;
        }
    }

    public static class DadCalculateStrategy implements CalculateStrategy {
        @Override
        public boolean isCalculable(String relationStatus) {
            return relationStatus.equals("dad");
        }

        @Override
        public int calculate(int points) {
            return points * 200;
        }
    }
}

//이런 식으로 확장포인트를 interface로 빼어서 확장에 강한 코드가 완성된다. Calculator는 interface에 의존하기 때문에 다형성을 활용해서 마음껏 확장할 수 있다. 

위에서 전략패턴이란 런타임 시간에 알맞는 알고리즘을 선정하는 방식이라 했다. 위에서는 relationStatus가 어떻게 들어오는지에 따라 CaluclatStrategy를 선정하기 때문에 전략패턴에 들어맞는다.

만약, relationStatus가 "friend", "granddad"와 같이 확장된다고 한다면 새로운 CalculatStrategy만 추가하고 List<CalculateStrategy>에 추가만 해주면 된다. 확장이 용이하게 된다.