[Design Pattern] 옵저버 패턴

이재훈·2023년 8월 25일
0

디자인패턴

목록 보기
5/6

자주 사용되는 디자인 패턴

옵저버 패턴

옵저버 패턴에서 다루고 있는 이벤트라는 개념은 프로그래밍 언어, 설계차원에서 널리 사용되고 있습니다. 옵저퍼 패턴은 어떤 대상의 상태 변화에 관심 있어하는 대상들에게 상태가 변화됐다고 전파할 수 있는 패턴(어떤 일에 대한 구독과 발행)입니다.

옵저버 패턴이 필요한 상황


어떤 서비스에서 게시글이 작성될 경우 아래의 기능을 제공합니다.

  • 문자 메시지 발송
  • 카카오톡 알림톡 발송
  • 이메일 발송

위의 내용을 코드로 작성해보도록 하겠습니다.

public ... createPost(...) {
	postRepository.save(...);
    
    smsSender.send(...);
    kakaotalkSender.send(...);
    emailSender.send(...);
}

이제 요구 사항이 바뀌었습니다. 바뀐 요구사항은 아래와 같습니다.

서비스에 댓글이 작성될 때도 메시지를 발송해야 합니다. 추가된 코드는 아래와 같습니다.

public ... createPost(...) {
	postRepository.save(...);
    
    smsSender.send(...);
    kakaotalkSender.send(...);
    emailSender.send(...);
}

public ... createComment(...) {
	commentRepository.save(...);
    
    smsSender.send(...);
    kakaotalkSender.send(...);
    emailSender.send(...);
}

DB에 저장하는 로직은 다르지만 아래 메시지를 전송하는 코드는 중복되게 됩니다. 이 코드는 리팩토링이 필요합니다. 간단한 방법부터 순서대로 진행해 보도록 하겠습니다.

public ... createPost(...) {
	postRepository.save(...);

	sendAlarm(...);
}

public ... createComment(...) {
	commentRepository.save(...);
    
    sendAlarm(...);
}

private void sendAlarm(...) {
	smsSender.send(...);
    kakaotalkSender.send(...);
    emailSender.send(...);
}

함수로 뽑아내어 중복을 제거하였습니다. 실제로는 다른 곳에서도 이 기능을 사용하는 것이 많을 것입니다. 그렇다면 별도의 서비스 클래스로 만들어야 합니다.

class AlarmService {
	public void sendAlarm(...) {
    	smsSender.send(...);
        kakaotalkSender.send(...);
        emailSender.send(...);
    }
}

이 코드는 퍼사드 패턴 같은 느낌을 줍니다. 이 코드도 나쁘지 않습니다. 하지만 여기서 요구사항이 추가되었습니다.

애플리케이션 실행 도중에 메시지를 보내는 매체를 추가, 제거가 가능해야 합니다. 기존 코드에서는 이렇게 작동하게 할 수 없습니다. 왜냐하면 각각의 매체별로 알림을 발송하는 코드가 고정적으로 작성되어 있기 때문입니다. 이럴 때는 옵저버 패턴을 사용하면 쉽게 해결할 수 있습니다.

이런식으로 구조가 바뀝니다. 각각의 매체들을 일종의 저장소에 등록을 하고 알림 매체를 직접 호출하는 것이 아닌 그 저장소를 사용하여 메시지를 전송하게 합니다.

옵저버 패턴 구현 코드

interface AlarmSubscriber {
	void send(...);
}
class AlrmPublisher {
	private List<AlarmSubscriber> alarmSubscribers = new ArrayList<>();
    
    public void sendAlarm(...) {
    	alarmSubscribers.forEach((subscriber) -> {
        	subscriber.send(...);
        });
    }

	public void addSubscriber(...) {
    	// alarmSubscribers에 새로운 알림 매체 추가 코드
    }
    
    public void addSubscriber(...) {
    	// alarmSubscribers에 해당 알림 매체를 제거하는 코드
    }
}
class SmsSender implements AlarmSubscriber {
	@Override
    public void send(...) {
    	// 문자 메시지 발송
    }
}

AlarmSubscriber 인터페이스를 만든 후 모든 매체들은 AlarmSubscriber 인터페이스를 구현하는 클래스로 생성을 합니다. AlrmPublisher 클래스는 알람을 보낼 매체를 추가, 삭제가 가능하고 등록된 매체들을 가져와서 send 메서드를 호출해 줍니다.

어떤 상황에 등록하고 제거하는가?

  1. 애플리케이션이 처음 시작 될 때 - 추가
  2. API로 - 추가 제거
  3. 장애 상황 try-catch - 제거

이벤트에 대해


게시글 작성과 댓글 작성은 이벤트가 발생되었다고 할 수 있습니다. 이벤트가 발생하면 이벤트 관리 객체에 의해 해당 이벤트를 구독하고 있던 주체들에게 이벤트를 전달해줍니다. 이렇게 이벤트가 발생했을 때 수행되는 일련의 로직들을 이벤트 핸들러, 이벤트 리스너 라고 합니다.

옵저버 패턴이 적용되면서 생긴 장점 & 보호하고자 하는 것

이벤트가 발생되는 곳에서 이벤트를 처리하는 주체들로의 의존성을 제거하였습니다. 또한 실행 시점에 이벤트 구독 대상을 추가하거나 제거가 가능하게 되었습니다. 이벤트를 발생 시키는 코드들을 보호하게 되었습니다. 이벤트 관리 객체에게 이벤트가 발생했다고만 알려주면 되는 것 입니다.

결론

옵저버 패턴도 결국에는 의존성을 최소화 하려는 패턴으로 보여집니다. 중간에 이벤트 관리 객체를 두게 되면 이벤트를 발생시키는 곳에서는 어떤 매체를 사용하는지 몰라도 되고, 이벤트 관리 객체에게 이벤트가 발생했다고 알려주기만 하면 됩니다. 스프링에서는 AOP를 사용하여 이벤트 관리 객체를 또 호출해줄 수 있다고 생각이 듭니다. 매체가 추가되거나 삭제되어도 이벤트를 발생시키는 클래스의 코드에는 영향을 주지 않습니다.


해당 게시글은 프로그래머스 스쿨 강의
"실무 자바 개발을 위한 OOP와 핵심 디자인 패턴(푸)"
를 정리한 내용입니다. 쉽게 잘 설명해주시니 여러분도 강의를 듣는 것을 추천드립니다.

profile
부족함을 인정하고 노력하자

0개의 댓글