회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
이번 글은 자바 디자인 패턴 시리즈의 마지막 편으로 행동 패턴(Behavior Patterns)에 대해 살펴보겠습니다. 행동 패턴은 이전 시리즈에서 살펴 본 구조 패턴(Structural Patterns), 생성 패턴(Creational Patterns)과 함께 디자인 패턴의 한 축을 담당하고 있는데요. 지금부터 실제 프로젝트에서 자주 활용되는 행동 패턴 종류를 알아보고, 프로젝트 적용 방법에 대해 살펴보겠습니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
이번 글은 자바 디자인 패턴 시리즈의 마지막 편으로 행동 패턴(Behavior Patterns)에 대해 살펴보겠습니다. 행동 패턴은 이전 시리즈에서 살펴 본 구조 패턴(Structural Patterns), 생성 패턴(Creational Patterns)과 함께 디자인 패턴의 한 축을 담당하고 있는데요. 지금부터 실제 프로젝트에서 자주 활용되는 행동 패턴 종류를 알아보고, 프로젝트 적용 방법에 대해 살펴보겠습니다.
행동 패턴은 객체의 상호작용과 책임 분산에 초점을 둔 디자인 패턴을 말합니다. 시스템 설계 시 행동 패턴을 적용하면 복잡한 객체 간 통신을 간소화하고, 각 객체의 독립성을 유지할 수 있습니다. 또한 객체를 독립적으로 다룰 수 있게 되어 테스트를 편리하게 진행할 수 있으며, 코드의 재사용성도 높일 수 있습니다.
일반적으로 사용되는 행동 패턴의 종류는 약 10가지 정도가 있습니다. 이번 글에서는 이 중에서 실제 프로젝트에서 자주 사용되는 커맨드 패턴(Command Pattern), 옵저버 패턴(Observer Pattern), 스테이트 패턴(State Pattern)을 살펴보려고 합니다.
각 패턴을 간단히 요약하면, 커맨드 패턴은 명령 또는 요청 자체를 객체로 캡슐화한 패턴을 말하며, 옵저버 패턴은 객체의 상태 변경을 다른 객체들에게 전파하는 패턴을 말합니다. 스테이트 패턴은 객체의 내부 상태에 따라 해당 객체의 행동을 변경하는 패턴입니다.
커맨드 패턴은 명령(Command)을 객체로 캡슐화하고, 이를 전달하는 호출자와 명령을 처리하는 수행자로 클래스를 분리하는 패턴을 말합니다. 시스템 설계에 커맨드 패턴을 적용하면 명령을 저장하여 나중에 실행하거나, 취소하고 다시 실행하는 등의 조작이 가능하여 보다 유연한 코드를 짤 수 있게 됩니다.
커맨드 패턴 예시로 다용도 리모컨을 구현한 코드를 만들어 보도록 하겠습니다. 우선, 다음과 같이 모든 커맨드 클래스가 구현해야 하는 Command 인터페이스를 정의합니다. 그러면 Command 인터페이스의 execute() 메서드를 오버라이딩(overriding)하여, 다양한 종류의 Command 클래스를 만들 수 있습니다. 이 예제에서는 execute() 메서드에 전등을 켜는 명령(Turn on)을 넣어 LightCommand를 생성했습니다.
.
다음으로 실제 전등을 의미하는 Light 클래스를 만들고, 리모컨을 나타내는 RemoteControl 클래스를 만듭니다. 여기서 Ligth 클래스는 수신자(Receiver) 클래스로 명령(command)을 받아 실제 작업을 처리하는 객체가 됩니다. RemoteControl 클래스는 호출자(Invoker) 클래스로서 클라이언트의 요청을 받아 수신자 클래스에 명령을 전달하는 역할을 합니다.
클라이언트 코드에서는 호출자인 RemoteControl 객체에 LightOnCommand를 설정한 후 해당 커맨드를 실행합니다. 즉, 리모컨 버튼을 누르면 전등의 불이 켜지게 됩니다.
이번에는 전등을 켜는 리모컨을 TV를 켜는 리모컨으로 바꿔보겠습니다. 먼저 아래 코드와 같이 다른 수신자 클래스인 TV 클래스를 만듭니다. 그리고 Command 인터페이스의 execute() 메서드를 오버라이딩하여, LightCommand와 유사하게 TVCommand 클래스를 생성합니다.
이제 아래 코드처럼 이전에는 전등용으로 사용하던 리모컨을 클라이언트 단에서 TV용 리모컨으로 바꿀 수 있습니다. 이처럼 커맨드 패턴을 이용하면, 호출자 객체에 다양한 커맨드를 설정하여 부품을 갈아끼듯이 사용할 수 있습니다. 이를 통해 호출자와 수신자 객체 간의 의존성을 줄이고, 각 코드의 재사용성을 높일 수 있는 것이죠.
옵저버 패턴은 객체의 상태나 데이터가 변경될 때마다 다른 객체에 알림을 보내는 패턴을 말합니다. 주로 이벤트 기반 아키텍처(Even Driven Architecture)에 활용되며, 이벤트 처리 시 객체 간의 결합도를 낮춰 독립적으로 동작할 수 있게 하는 장점이 있습니다. 옵저버 패턴은 Subject와 Observer 인터페이스 그리고 각 인터페이스를 구현한 클래스들로 구성되는데요. Subject는 상태나 데이터가 변경될 때 Observer에 알려주는 역할을 하며, Observer는 Subject의 변경을 관찰하고 알림을 받는 대상이 됩니다.
옵저버 패턴의 예시로 날씨 정보 디스플레이를 구현해 보겠습니다. 먼저, 옵저버 패턴을 적용하기 위해서 다음과 같이 Subject와 Observer 인터페이스를 정의합니다. 그리고 Subject 인터페이스에서는 옵저버를 관리하고 알림을 보내는 메서드들을 정의하고, Observer 인터페이스에서는 Subject로부터 받은 정보를 업데이트하는 update() 메서드를 정의합니다.
다음으로 기상 데이터(온도와 습도)를 관리하고, 변경된 정보를 옵저버에 알리는 WeatherStation 클래스를 만듭니다. WeatherStation 클래스는 Subject 인터페이스를 구현하여 옵저버를 등록하거나 삭제할 수 있습니다. 또한 기상 데이터가 업데이트되면, 옵저버 리스트에 있는 각 옵저버에 변경된 정보를 통보하게 됩니다.
이제 WeatherStation의 업데이트된 기상 정보를 받아 디스플레이에 출력하는 WeatherScreen이라는 클래스를 만듭니다. WeatherScreen 클래스는 Observer 인터페이스의 update() 메서드를 오버라이딩합니다.
클라이언트 코드에서는 아래와 같이 WeatherScreen 객체를 생성하고, 그 객체를 WeatherStation 옵저버 리스트에 등록합니다. 그러면 이제 WeatherStation에서 기상 데이터가 변경될 때마다 WeatherScreen의 데이터도 같이 변경되는 것을 볼 수 있습니다.
마지막으로 WeatherScreen 객체를 WeatherStation 옵저버 리스트에서 제거한 후, 다시 display() 메서드를 호출해 봅니다. 그러면 WeatherScreen은 WeatherStation의 옵저버 리스트에 없기 때문에, 업데이트된 정보를 더 이상 받지 않으며 이전에 표시된 값이 출력됩니다. 이처럼 옵저버 패턴을 적용하면 특정 객체의 업데이트 정보를 선택적으로 어떤 객체에게 전달할지 효과적으로 관리할 수 있습니다.
스테이트 패턴은 객체의 상태에 따라 동적으로 행동을 변경할 수 있게 해주는 디자인 패턴입니다. 스테이트 패턴을 적용하면 if나 switch문으로 지저분한 분기 코드를 만드는 것보다, 코드의 가독성을 향상시키고 확장성을 높일 수 있습니다. 또한 각 상태가 자신의 행동을 캡슐화함으로써 단일 책임 원칙(Single Responsibility Principle)을 준수하고, 코드 유지 보수를 용이하게 하는 장점이 있습니다.
스테이트 패턴의 예시로 사용자의 로그인 상태에 따라 다르게 동작하는 코드를 작성해 보겠습니다. 먼저 스테이트 패턴을 적용하기 위해 UserState 인터페이스를 만듭니다. UserState 인터페이스에서는 사용자의 상태에 따른 login()과 logout() 메서드를 정의하는데요. 이 메서드들은 사용자 상태 변화에 따라 다르게 동작하는 메서드들입니다.
여기서 LoggedInState 클래스와 LoggedOutState 클래스는 UserState 인터페이스를 구현한 구체적인 상태 클래스(Concrete State Class)입니다. 이 클래스들을 통해 User의 구체적인 상태들을 구분하고 각 상태에서의 동작을 구현합니다.
이어서 UserContext 클래스를 구현합니다. 이 클래스는 사용자의 현재 상태를 추적하며, 상태 변화 시 setState() 메서드를 이용해 해당 상태를 업데이트합니다. 또한 사용자의 로그인 상태에 따라 login()과 logout() 메서드가 다르게 동작하도록 정의하죠.
클라이언트 코드에서는 UserContext 객체를 생성하고, 이 객체를 통해 로그인과 로그아웃 요청을 합니다. 그러면 UserContext의 setState() 메서드를 통해 로그인 상태가 변경되고, login()과 logout() 메서드를 통해 현 상태에 따른 동작을 수행하게 됩니다.
이처럼 스테이트 패턴을 적용하면, 클라이언트는 User의 구체적인 상태를 몰라도 UserContext만 호출하면 됩니다. 즉, 자동으로 현재 로그인 상태에 맞는 동작을 실행하게 되는 것이죠. 결과적으로 클라이언트 코드와 User의 상태 관리 코드는 서로 분리되어 코드의 유지 보수가 더욱 간편해지게 됩니다.
커맨드 패턴은 다양한 명령의 실행 및 취소, 로깅, 트랜잭션 등의 기능이 필요할 때 적용을 고려해 볼 수 있습니다. 특히 복잡한 호출 구조를 갖는 여러 작업을 추상화하고 캡슐화해야 할 때 커맨드 패턴이 유용한데요. 이 밖에도 시스템의 명령을 대기열에 저장하거나, 로직의 요청자와 수행자를 분리해야 하는 경우에도 커맨드 패턴을 적용하는 것이 좋습니다.
다만 커맨드 패턴 사용하면 각 명령에 대한 클래스를 개별적으로 생성해야 하므로 클래스의 수가 급격히 늘어날 수 있습니다. 이로 인해 코드가 복잡해지고, 프로젝트에 새로 투입된 개발자가 이해하기 어려운 코드가 될 수도 있습니다. 따라서 커맨드 패턴을 적용할 때는 해당 패턴이 적용된 범위를 잘 파악하고 관리하는 것이 중요하며, 무분별하게 패턴을 남용하지 않도록 해야 합니다.
프로젝트에서 한 객체의 상태 변화가 다른 객체들에게 영향을 주는 경우 옵저버 패턴의 적용을 고려해 볼 수 있습니다. 특히 상태 변화에 따른 알림이나 업데이트를 자동화하고자 할 때 옵저버 패턴이 매우 유용한데요. 동적으로 관찰자를 추가하거나 제거해야 하는 상황에서도 옵저버 패턴은 효과적인 디자인 패턴입니다.
하지만 Subject 클래스에 등록된 옵저버가 과도하게 많아지면, 알림을 보내는 과정에서 성능 문제가 발생할 수도 있습니다. 또한 옵저버 패턴 적용 시 잘못된 설계나 구현의 실수로 클래스 간의 순환 의존성이 발생하는 경우도 있기 때문에 주의가 필요합니다.
스테이트 패턴은 객체의 내부 상태에 따라 동작이 변경되어야 하는 경우 적용하기 좋습니다. 특히, if나 switch문으로 3가지 이상의 상태를 분기해서 처리하던 코드를 리팩토링(refactoring) 할 때도 유용하게 사용할 수 있는데요. 객체의 상태 변화 로직을 명확히 분리하고, 상태 관리를 조금 더 직관적으로 할 수 있기 때문입니다.
다만 스테이트 패턴도 커맨드 패턴과 마찬가지로 각 상태에 따른 동작을 별도의 클래스로 분리해야 하기 때문에, 클래스의 수가 과도하게 증가할 수 있습니다. 이로 인해 전체 시스템의 복잡도가 증가하고 코드의 가독성이 떨어질 수 있습니다. 따라서 패턴을 적용하기 전에 스테이트 패턴의 이점이 그에 따르는 복잡도와 비용을 상쇄하는지 따져봐야 합니다.
이번 글에서는 자바 디자인 패턴 시리즈의 마지막으로 행동 패턴의 종류와 주요 특성, 그리고 실제 프로젝트에서의 적용 방법에 대해서 살펴봤습니다. 특히 이번에 다룬 커맨드 패턴, 옵저버 패턴, 스테이트 패턴은 실무에서도 흔히 활용되기 때문에 익혀두는 것이 좋습니다. 아울러 이외에도 전략 패턴(Strategy Pattern)과 템플릿 메소드 패턴(Template Method Pattern)도 자주 쓰이는 행동 패턴이니 따로 학습해두는 것을 권장합니다.
지금까지 자바 디자인 패턴 시리즈로 총 4편의 글을 정리했습니다. 어댑터 패턴을 시작으로 자바 디자인 패턴에 대해 알아보고, 이후 2,3,4편에서는 디자인 패턴의 세 축인 구조 패턴, 생성 패턴, 행동 패턴에 대해서 각 패턴의 종류와 프로젝트 적용 방법을 살펴보았습니다.
디자인 패턴은 코드 가독성을 높이고 유지 보수를 용이하게 할 수 있기 때문에 개발자로서 꼭 공부해두는 것이 좋습니다. 다만 디자인 패턴은 그 자체로 100% 완전무결한 것이 아니므로, 각 패턴의 장단점과 프로젝트 적용 시 주의할 점들도 함께 알아두길 바랍니다.
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.