티스토리 뷰

knowledge/pattern

[Pattern] Strategy Pattern

글을 쓰는 개발자 2022. 8. 10. 07:02
반응형

정의

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다. 
전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있다.

정의만 봐서는 바로 이해가 되지 않을 것이다.

 

책을 읽으면서 제가 재정의한 Strategy Pattern은 다음과 같다.

어떠한 한 객체가 동작하는 데 있어서 여러 동작을 할 수가 있는데, 이를 유동적으로 변환하는 패턴

이 또한 바로 봐서는 "무슨 소리지?" 라고 할 수 있다.

우선 전략패턴을 사용하지 않았을 때 좋지 않은 케이스를 보도록 하겠다.

 

상속

상속을 통한 구현

현재 Person 이라는 클래스를 만들고 이에 대하여 하위 세 개의 클래스는 상속을 통해 구현한 모습을 볼 수 있습니다.

public class Person {

    public void eat() {
        System.out.println("삼겹살");
    }
}

 

Person.java 에서 다음과 같이 eat 메소드를 정의하고 있다.

 

이 때 Vegetarian에서 eat을 하게 되면 '삼겹살'을 먹게 되며 게임이나 기타 다른 서비스에서 이런식으로 동작을 하게 된다면 어떤 일이 벌어질까? 서비스에 있어서 심각한 문제가 발생될 수 있다.

 

만약에 삼겹살이 아니고 땅콩 이라면?

 

그러면 땅콩 알러지 가진 사람들한테 있어서 심각한 문제를 야기할 수 있다.

 

이렇게 composite을 하지 않고 상속을 하게 되면 다음과 같은 문제점을 가지게 된다.

 

  • 실행 시에 특징을 바꾸기 힘들다.
  • 코드를 변경했을 때 다른 객체들에게 원치 않은 영향을 끼칠 수 있다.

 

그렇다면 어떻게 구현하면 좋을까?

 

이렇게 생각할 수 있다.

Eatable이라는 인터페이스를 구현하고 이에 대해서 implements를 함으로써 처리하는 건 어떨까?

다음과 같이 진행하게 된다면 "코드를 변경했을 때 다른 객체들에게 원치 않은 영향을 끼칠 수 있다." 이 부분을 해결할 수 있다.

하지만 다른 문제점이 생기게 된다. 

 

바로 중복된 코드가 많아지게 된다는 것이다.

 

비록 당장 위의 3개의 클래스에서 사용하는 것이 어렵지 않다. 하지만 저것보다 더 큰 숫자의 클래스가 있다고 생각해보자.

감당하지 못할 양일 것이다.

 

다시 한 번 고민

 

디자인 원칙 중 하나는 "애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다."를 고려해줘야 한다.

 

즉, 이 말은 "캡슐화"를 의미하기도 한다.

 

 

전략 패턴의 사용

 

 

행동을 규격화하는 것이 중요하다.

Eatable 이라는 인터페이스를 위와 같이 구성하고 이에 대한 구현된 행동들을 정의한다.

 

핵심

실제 실행 시에 쓰이는 객체가 코드에 고정되지 않도록 상위 형식에 맞춰 프로그래밍해서 다형성을 활용해야 한다는 점

 

변화된 Person 클래스

public class Person {
    Eatable eatable;

    public Person(Eatable eatable) {
        this.eatable = eatable;
    }
    
    public void eat() {
        eatable.eat();
    }

    public void setEatable(Eatable eatable) {
        this.eatable = eatable;
    }
}

 

다음과 같이 구현하게 되면 상황에 따라 다양하게 먹을 수 있는 Person이 나오게 된다.

 

 

스프링에서 존재하는 Strategy Pattern?

스프링에서 전략패턴을 사용하는 곳이 어디에 있나 실제 오픈소스를 확인해가며 확인했었다.

완벽한 전략패턴이라고 보기는 힘들지만 유사한 것 같아 이렇게 소개하고자 한다.

 

@ImportRuntimeHints(MethodValidationRuntimeHints.class)
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
		implements InitializingBean {

	@Nullable
	private Validator validator;

	public void setValidator(Validator validator) {
		// Unwrap to the native Validator with forExecutables support
		if (validator instanceof LocalValidatorFactoryBean) {
			this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
		}
		else if (validator instanceof SpringValidatorAdapter) {
			this.validator = validator.unwrap(Validator.class);
		}
		else {
			this.validator = validator;
		}
	}

	public void afterPropertiesSet() {
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}

스터디 하면서 나왔던 이야기들

인터페이스를 통해 해당 행위를 주입하는 방식으로 동작하는 스프링이 있는데 전략패턴과 해당 방식의 차이점이 뭐가 있을까요?

각 의견들

  1. 해당 내용은 SOLID 원칙 중 D에 해당하는 의존 역전의 법칙에 더 맞는 설명인 것 같다. 구체 클래스에 의존하지 않고 인터페이스에만 의존한다는 것에서 그렇게 느꼈다.
  2. 전략패턴은 결국 compoiste 한 인터페이스에 대한 행위를 실행한다는 것이 스프링에서 인터페이스를 통한 주입하는 것과 차이가 있다고 생각한다. Eatable이라는 인터페이스에 있는 eat 메소드를 결국 Person에서의 eat 메소드 안에서 호출함으로써 해당 행위를 구현한 것이고, 스프링에서의 인터페이스를 통한 주입은 가령 예를 들어 서비스에서 레포지토리를 주입을 받는데 서비스가 레포지토리의 행위를 대신 해주는 것은 아니지 않나? 라고 생각한다. 윗분과 비슷한 의견을 가진다.

 

 

반응형

'knowledge > pattern' 카테고리의 다른 글

[Pattern] Decorator Pattern  (0) 2022.09.10
[Pattern] Observer Pattern  (0) 2022.08.27
[Pattern] Facade Pattern  (0) 2021.12.12
[Pattern] Composite Pattern에 대하여  (0) 2021.11.29
[Pattern] Bridge Pattern에 대하여  (0) 2021.11.23
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함