본문 바로가기
Spring Study

Delegate Pattern (위임 패턴)

by xogns93 2024. 8. 28.

"Delegate"라는 개념은 다양한 프로그래밍 언어와 프레임워크에서 사용되며, 메서드 호출을 다른 객체나 메서드로 위임하는 패턴을 의미합니다. Java에서의 위임(delegate)은 특정 인터페이스의 메서드를 직접 구현하지 않고, 그 구현을 다른 객체에 위임(delegate)하는 방식으로 구현할 수 있습니다. 이를 통해 코드 재사용성과 유연성을 높일 수 있습니다.

 

Delegate Pattern

  • delegate는 '위임하다' 라는 사전적 의미
  • 즉, 객체가 자신의 기능을 다른 객체에게 위임하여 기능을 실행하는 디자인 패턴
  • 객체 간의 결합도를 낮춰서 유지보수성과 확장성을 높이는데 사용됨

예시 : 

  • A는 B의 기능을 사용하고 싶을 때, A가 B의 기능을 직접 구현하지 않고, C에게 위임(delegate)하여 구현하는 것
  • 이 때 C를 델리게이트(delegate) 객체 or 대리자 객체라고 함
  • 델리게이트 객체는 B의 기능을 대신 구현하기 때문에, A는 C를 통해 B의 기능을 호출할 수 있음
  • 이를 통해 A는 B의 기능을 사용할 수 있으면서도, A와 B의 결합도를 낮출 수 있음

위 예시를 좀 더 구체적으로 말해보면 다음과 같습니다 : 

  • A가 동물의 울음소리를 출력하고 싶음
  • B는 다양한 동물이 존재함
  • A는 B에 직접적으로 접근하지 않고, 울음소리를 출력하는 기능을 C에게 위임
  • C는 B에서 적절한 동물의 울음소리를 선택하여 출력할 수 있음
  • 이처럼 A와 B의 결합도를 낮추면서 다양한 동물의 울음소리를 출력할 수 있음

 

Delegate 패턴을 적용한 예시코드

public enum AnimalKind {
    CAT, DOG
}

public interface Animal {
    void makeSound();
    AnimalKind getKind();
    boolean isSameKind(AnimalKind kind);
}

// B
public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹");
    }
    
    @Override
    public AnimalKind getKind() {
    	return AnimalKind.CAT;
    }
    
    @Override
    public boolean isSameKind(AnimalKind kind) {
    	return AnimalKind.CAT.equals(kind);
    }
}

// B
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍");
    }

    @Override
    public AnimalKind getKind() {
    	return AnimalKind.DOG;
    }
    
    @Override
    public boolean isSameKind(AnimalKind kind) {
    	return AnimalKind.DOG.equals(kind);
    }
}

// C
public class AnimalDelegator {
	private final List<Animal> animals;
    
    public AnimalDelegator() {
    	animals = new ArrayList<>();
    	animals.add(new Cat());
        animals.add(new Dog());
    }

    public void makeSound(AnimalKind kind) {
        animals.stream()
                .filter(animal -> animal.isSameKind(kind))
                .forEach(Animal::makeSound);
    }
}

// A
public class Client {
    public static void main(String[] args) {
        AnimalDelegator delegator = new AnimalDelegator();
        delegator.makeSound(AnimalKind.CAT); // "야옹" 출력
        delegator.makeSound(AnimalKind.DOG); // "멍멍" 출력
    }
}
  • A는 `Client` 클래스
  • B는 `Cat`와 `Dog` 클래스
  • C는 `AnimalDelegator` 클래스
  • A는 C의 makeSound()를 호출함으로써 동물의 울음소리를 출력
  • A는 B에게 직접적으로 접근하지 않고, 울음소리를 출력하는 기능을 C에게 위임
  • C는 B에서 적절한 동물의 울음소리를 선택하여 출력

 

Delegate 패턴과 Decorator 패턴 차이점

Decorator 패턴

  • 객체에 동적으로 기능을 추가 또는 변경할 수 있는 패턴
  • "객체가 가진 기본 기능을 변경하지 않으면서 기능을 추가하는 것"이 핵심
  • 기본 객체를 래핑(Wrapping)하여 추가적인 기능을 제공하고, 이를 계속 래핑하여 여러 개의 데코레이터가 중첩될 수 있음
  • 상속을 이용하여 구현하는 경우가 많음

 

Delegate 패턴

  • 객체의 행위를 다른 객체에 위임하는 패턴
  • "객체가 다른 객체에게 일부 기능을 위임할 수 있도록 하는 것"이 핵심
  • 객체가 직접적으로 기능을 수행하는 것 대신에 다른 객체에 해당 기능을 위임
  • 인터페이스를 이용하여 구현하는 경우가 많음

 

결론

  • Decorator 패턴은 객체의 기능을 동적으로 확장하는 것이 목적
    • 원래 객체는 변하지 않으며 새로운 Decorator 객체를 사용할 수 있음
  • Delegate 패턴은 객체의 동작을 다른 객체에 위임하는 것이 목적
    • 원래 객체의 동작이 변경될 수 있음

 

Delegate 패턴과 Strategy 패턴 차이점

Strategy 패턴

  • 동일한 문제를 해결하기 위한 알고리즘을 캡슐화하고, 이를 교환해서 사용하는 패턴
  • 즉, 알고리즘을 인터페이스로 정의하고 이를 구현한 다양한 전략 객체를 필요에 따라 교체하여 사용
  • 전략 객체들은 같은 문제를 해결하기 위한 다양한 알고리즘으로, 서로 교체 가능하며, 동적으로 알고리즘을 변경 가능

 

결론

  • Stragegy 패턴은 실행 중에 알고리즘을 변경하기 위한 패턴
    • e.g. 할인 적용 시, 다양한 할인 전략에 따라 가격이 달라질 때
  • Delegate 패턴은 객체의 동작을 다른 객체에 위임하여 사용하기 위한 패턴
    • e.g. 주문 처리시, 여러 단계로 나누어 각 단계에서 다른 로직을 처리할 때