[Design Pattern] 옵저버 패턴(Observer Pattern)에 대해서 - 컴도리돌이
어떤 애플리케이션이든 데이터를 관리하는 객체가 있고, 그 데이터를 사용하는 다른 객체들이 있습니다. 데이터를 가진 객체가 상태를 변경했을 때, 그와 연결된 다른 객체들도 그 변화를 알아야 한다면, 어떻게 이 문제를 해결할 수 있을까요? 🤔
초기에는 데이터 객체가 모든 연결된 객체들을 직접 참조하면서 상태 변경을 통보하는 방식이 사용되었을지도 모릅니다. 하지만 이렇게 하면 문제가 생겨요. 🤨 객체 간의 의존성이 강해져, 하나의 변경이 시스템 전체에 영향을 미치게 되고, 코드가 복잡해지며 유지보수가 어려워지죠.
그래서 옵저버 패턴이 등장하게 돼요. 옵저버 패턴은 한 객체의 상태 변화가 있을 때, 그와 관련된 다른 객체들에게 자동으로 알려주는 구조를 제공해 줘요. 객체들은 서로 느슨하게 결합되어 있고, 이로 인해 코드의 유연성과 재사용성이 높아지게 되는 거죠. 👍
아래의 예시코드를 보면서 옵저버 패턴에 대해서 조금 더 이해해 볼게요 😊
아래 코드를 보면, 해당 클래스는 이벤트를 표현하는 데 사용돼요. 어떤 일이 발생했을 때, 그 일에 대해 다른 부분에서 반응할 수 있도록 신호를 보내는 거죠. 아래 코드에서는 어떤 일은 '온도 변화'에 해당되겠죠? 이 이벤트 클래스는 ApplicationEvent를 상속받아 만들어졌는데, 여기서 왜 ApplicationEvent를 사용해야 할까요? 이는 스프링이 이벤트를 관리하고 전달하는 표준적인 방법을 제공하기 때문입니다. ✅
class TemperatureChangeEvent extends ApplicationEvent {
private final float temperature;
public TemperatureChangeEvent(Object source, float temperature) {
super(source);
this.temperature = temperature;
}
public float getTemperature() {
return temperature;
}
}
온도를 저장하는 temperature라는 변수가 있고, 이 변수를 초기화하는 생성자가 있습니다. 이벤트가 발생할 때, 우리는 이 temperature 값을 전달해서 그 변화를 다른 객체들이 알 수 있도록 해야 해요 🤔 그렇기게 변화를 정확하게 반영하기 위해서 getTemperature 메서드를 사용해서 값을 전달시켜 줍니다.
@Component
class WeatherData {
private final ApplicationListener<TemperatureChangeEvent> listener;
public WeatherData(ApplicationListener<TemperatureChangeEvent> listener) {
this.listener = listener;
}
public void setTemperature(float temperature) {
listener.onApplicationEvent(new TemperatureChangeEvent(this, temperature));
}
}
WeatherData 클래스를 보면, 온도 데이터를 관리하고, 온도가 바뀔 때마다 이벤트를 생성하는 역할을 해요. 그런데 여기서 왜 이벤트를 생성해야 할까요?? 🙄 변화가 있을 때 다른 객체들이 그 변화를 알아차리고 적절히 대응할 수 있도록 하기 위해서입니다. 그래서 온도가 바뀌면 새로운 TemperatuerChageEvent를 생성하고, 등록된 리스너에게 이 이벤트를 전달해 줘요. 👍
@Component
class CurrentConditionsDisplay implements ApplicationListener<TemperatureChangeEvent> {
@Override
public void onApplicationEvent(TemperatureChangeEvent event) {
display(event.getTemperature());
}
public void display(float temperature) {
System.out.println("현재 온도: " + temperature + "°C");
}
}
CurrentConditionDisplay 클래스는 ApplicationListener <TemperatureChangeEvent>를 구현해요. 그러면 TemperatuerChangeEvent가 발생하면 이 클래스가 반응하게 되겠죠. 이벤트가 발생하면 onApplicationEvent 메서드가 호출되고, 이 메서드에서 온도 정보를 받아와서 display 메서드로 화면이 출력하게 되고, 이제 온도가 바뀔 때마다 최신 정보를 사용자에게 보여줄 수 있게 됩니다. 👍
마지막으로 스프링 애플리케이션이 시작되면, 스프링 컨텍스트가 생성하게 되고, 여기서 WeatherData 빈을 가져와서, 온도를 변경하면 옵저버에게 자동으로 알림이 가도록 해줍니다. 이런 구조를 사용하면 스프링의 의존성 주입 기능을 활용해 객체들을 효율적으로 관리하고, 그 객체들 간의 상호작용을 자연스럽게 처리할 수 있게 되겠죠.
@SpringBootApplication
public class WeatherStationApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(WeatherStationApplication.class, args);
WeatherData weatherData = context.getBean(WeatherData.class);
// 온도가 변하면 옵저버에게 자동으로 알림
weatherData.setTemperature(25.0f);
}
}
MSA에서 서비스를 설계할 때, 서로 다른 서비스들 간에 이벤트를 통해 통신하는 경우가 많은데, 예를 들어 사용자가 주문을 하면 주문 서비스에서 주문에 대한 이벤트를 발생시키고, 이 이벤트를 다른 서비스들이 구독해서 처리할 수 있겠죠. 또한 결제 서비스에서 이 이벤트를 받아 결제를 처리하고, 재고 서비스에서는 재고를 업데이트를 할 수 있죠. 이렇게 서비스 간의 결합도를 낮추고, 변경에 유연하게 대응할 수 있는 구조를 만들어 줍니다. 그럼 굳이 이 패턴을 적용하는 이유가 뭘까요? 서비스들이 서로 독립적으로 동작하면서도, 중요한 상태 변화에 대해 협력할 수 있는 장점이 있기에 이런 패턴을 적용되는 게 아닐까요? 🤔
옵저버 패턴은 객체 간의 관계를 유연하게 만들어, 변화에 쉽게 대응할 수 있게 해주는 패턴이었어요. 이 패턴을 사용하면 복잡한 시스템을 설계할 때, 더욱 단순하게 만들 수 있을 거 같아요😊 다음번 코드를 작성할 때는 옵저버 패턴을 떠올려봐야겠어요 🫣