알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든 패턴. 스트래티지 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할수 있습니다.
일반적으로 서브클래스를 만드는 방법을 대신하여 유연성을 극대화시키는 용도로 사용합니다.
디자인원칙 중 변경이 필요한 부분의 분리, 상속보다는 구성을 활용하라는 원칙을 적용한 패턴입니다.
EX) sorting 알고리즘 여러개가 있는데 이중 하나를 고르려고 할때
위 클래스 다이어그램은 스트래티지 패턴의 가장 적합한 예들 중 하나입니다.
로봇의 종류에는 TaewonV, Atom, Sungard가 있으며, 각 로봇의 공격과 움직임에 대해 조립하듯이 끼워맞출 수 있습니다.
만약 TaewonV에 날수있는 기능과 펀치기능을 넣고싶다면 new FlyingStrategy()와 new PunchStrategy()을 추가할 수 있고
Robot taekwonV = new TaekwonV("TaekwonV");
taekwonV.setMovingStrategy(new FlyingStrategy());
taekwonV.setAttackStrategy(new PunchStrategy());
펀치기능이 아닌, 미사일 공격을 하고싶다면 new MissileStrategy()로 변경할 수 있습니다.
taekwonV.setAttackStrategy(new MissileStrategy());
위와 같은 방식으로 TaewonV의 공격과 움직임에 대한 변경이 기존 코드를 건드리지 않고 추가할 수 있기에 OCP를 만족하는 설계가 됩니다.
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html
사실 예제로 만들어진 스트래티지 패턴에 대한 예시는 다양합니다. 그렇다면 JDK에서 발견할수 있는 스트래티지 패턴에는 어떤것이 있을까요?
Collections.sort(List list, Comparator<? super T> c)
위 메소드는 JDK에서 확인할수 있는 대표적인 스트래티지 패턴입니다.
아래 예시를 통해 확인하겠습니다.
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(3, 2, 1, 4, 5);
//오름차순
Collections.sort(list, Comparator.naturalOrder());
System.out.println("결과값 : " + list);
// 결과값 : [1, 2, 3, 4, 5]
//내림차순
Collections.sort(list, Comparator.reverseOrder());
System.out.println("결과값 : " + list);
// 결과값 : [5, 4, 3, 2, 1]
}
}
결과값 : [1, 2, 3, 4, 5]
결과값 : [5, 4, 3, 2, 1]
Collections.sort() 메소드에서 스트래티지 패턴이 적용된 부분은 Comparator.naturalOrder()와 Comparator.reverseOrder() 입니다.
sort하는 과정에서 List의 값을 오름차순 알고리즘을 적용할지, 내림차순 알고리즘을 적용할지 사용자가 판단하여 손쉽게 적용할 수 있습니다.
그리고 사용자가 새로운 알고리즘을 추가하고싶다면 추가 class를 작성하여 적용할수 있습니다.
아래는 사용자가 커스텀한 알고리즘을 적용한 소스입니다. (나이에 대해서 ASC를 적용하고 같은 나이라면 이름으로 DESC 합니다.)
public class Employee implements Comparable<Employee>{
private int id;
private String name;
private int age;
public Employee(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Employee o) {
if(o.getId() > this.getId()) {
return 1;
} else if(o.getId() < this.getId()) {
return -1;
}
return 0;
}
}
public class Main {
public static void main(String[] args) {
List<Employee> emps = new ArrayList<>();
emps.add(new Employee(2001, "Modi", 51));
emps.add(new Employee(1901, "Trumph", 57));
emps.add(new Employee(1945, "Boris Johnson", 56));
emps.add(new Employee(1901, "Sabina", 57));
emps.add(new Employee(1835, "Mary", 70));
emps.add(new Employee(1984, "Isabella", 46));
emps.add(new Employee(2001, "Ava", 51));
Collections.sort(emps, (o1, o2) -> {
if(o1.getAge() == o2.getAge()) {
return o2.getName().compareTo(o1.getName());
}
return o1.getAge() - o2.getAge();
});
for(Employee emp : emps) {
System.out.println("ID => " + emp.getId() + ", name => " + emp.getName() + ", age => " + emp.getAge());
}
}
}
ID => 1984, name => Isabella, age => 46
ID => 2001, name => Modi, age => 51
ID => 2001, name => Ava, age => 51
ID => 1945, name => Boris Johnson, age => 56
ID => 1901, name => Trumph, age => 57
ID => 1901, name => Sabina, age => 57
ID => 1835, name => Mary, age => 70
위 예시에서는 lambda식으로 알고리즘을 바로 적용하였지만, 아래 소스를 따로 class로 분리하여 사용하면 또 하나의 전략이 생성되는 것입니다.
if(o1.getAge() == o2.getAge()) {
return o2.getName().compareTo(o1.getName());
}
return o1.getAge() - o2.getAge();
https://gmlwjd9405.github.io/2018/07/06/strategy-pattern.html
https://antkdi.github.io/posts/post-java-custom-comparator-sorting/
https://refactoring.guru/design-patterns/strategy/java/example