1. 책임 연쇄 패턴(Chain of Responsibility Pattern)
책임 연쇄 패턴은 요청을 처리하는 인터페이스를 정의하고 다수의 객체를 체인 형태로 연결하여 책임을 부여하는 패턴입니다. 각각의 객체들은 고유의 기능을 가지고 있으며, 사용자의 요청을 처리하지 못하는 경우 책임을 다음 체인으로 넘깁니다.
Handler는 처리 객체들의 인터페이스이며, ConcreteHandler들은 클라이언트의 요청에서 처리할 수 있는 부분만 처리하고 처리하지 못하는 부분은 다른 ConcreteHandler에게 책임을 넘깁니다.
예제코드를 작성해 보겠습니다. 어플리케이션이 있고 백엔드 개발자, 프론트엔드 개발자, 데이터베이스 개발자가 자신의 코드를 작성하는 시나리오 입니다.
Handler.java: Handler 인터페이스 클래스입니다. handlerRequest 메소드는 final 키워드를 통해 overide가 불가능하도록 만들었습니다. 개발자 클래스는 Handler를 상속받고 handle 메소드를 구현하게 됩니다. setNextHandler 메소드는 빌더 패턴으로 연쇄적인 체인을 형성할 수 있도록 했습니다.
Backend.java:
Database.java:
Frontend.java:
클라이언트 요청을 처리하는 concreteHandler 클래스들 입니다. 각각 백앤드, 데이터베이스, 프론트엔드 코드를 작성합니다.
App.java: 어플리케이션 클래스입니다. 중요한 부분은 makeApp 메소드 입니다. 퍼샤드 패턴으로 어플리케이션 코드를 작성합니다. Backend 핸들러를 우선 생성하고 그 아래 연쇄 체인을 설정합니다. 백엔드 -> 데이터베이스 -> 프론트엔드 순서로 체인이 만들어졌습니다. 이제 각 체인을 순회하며 각각의 코드들이 작성됩니다.
Client.java:
클라이언트 클래스입니다. 어플리케이션 인스턴스를 생성하고 makeApp 메소드를 통해 코드를 작성하면 다음과 같이 출력됩니다.
이번에는 프론트엔드를 미리 작성한 후 앱을 만들어보겠습니다.
보는 바와 같이 작성되지 않은 백엔드 코드와 데이터베이스 코드만 작성합니다.
책임 연쇄 패턴은 코드를 변경하지 않고 새로운 핸들러를 체인에 추가할 수 있으며, 순서도 지정할 수 있으므로 개방폐쇄원칙(OCP)를 만족합니다. 또한 각각의 핸들러들은 한가지 역할만 수행하므로 단일책임원칙(SRP)도 만족합니다.
단점으로는 다양한 체인을 거치게 되므로 디버깅이 어렵고 무한루프에 빠질 가능성이 있습니다. 쉽게 말해서 책임을 떠넘기다보니 뺑뺑이를 도는겁니다.
자바에서 try - catch 구문은 책임 연쇄 패턴으로 작성되어 있습니다.
2. 커맨드 패턴(Command Pattern)
커맨드 패턴은 사용자의 요청을 객체 형태로 캡슐화하는 패턴입니다. 전략 패턴과 유사하지만, 전략 패턴은 동일한 오퍼레이션을 로직만 다르게 하는 패턴인 반면에, 커맨드 패턴은 오퍼레이션 자체를 캡슐화하여 객체로 만들어 다른 오퍼레이션을 수행하는 패턴입니다. 오퍼레이션을 커맨드로 객체화 했기때문에 정보를 저장하거나 로깅, Undo 또한 구현할 수 있습니다.
Command 패턴은 명령(command), 수신자(receiver), 발동자(invoker), 클라이언트(client)로 구성됩니다. 커맨드 객체는 수신자 객체를 가지고 있으며, 수신자의 action 메소드를 호출합니다. action 메소드가 호출된 수신자는 자신에게 정의된 메소드를 수행하게 됩니다. 또한 커맨드 객체는 별도로 발동자 객체에 전달되어 명령을 저장하며 이때 발동자 객체는 필요에 따라 명령 발동에 대한 로그를 남김으로써 Undo가 가능하게 됩니다. 클라이언트 객체는 발동자 객체와 하나 이상이 커맨드 객체를 가지고 있으며, 어느 시점에 어떤 명령을 수행할지를 결정합니다.
예제 코드를 작성해보겠습니다. 장소에 따른 전등을 끄고 켜는 시나리오 입니다.
Command.java: 커맨드 객체의 인터페이스 입니다.
TurnOffLightCommand.java: 전등을 끄는 커맨드 객체 입니다. 생성자가 리시버인 Light를 매개변수로 받아 필드로 저장합니다.
TurnOnLightCommand.java: 전등을 켜는 커맨드 객체 입니다. 마찬가지로 생성자가 리시버인 Light를 매개변수로 받아 필드로 저장합니다.
Light.java: 명령을 수행하는 리시버 객체입니다. 생성자가 location을 매개변수로 받아서 장소마다 다른 전등 객체를 생성할 수 있습니다.
LightInvoker.java: 리시버에게 명령을 내리는 인보커 객체입니다.
Client.java: 2개의 리시버(거실 전등과 화장실 전등)를 만들었습니다. 리시버에 대한 커맨드를 각각 작성하고 인보커도 작성해줍니다. 인보커를 통해 리시버에게 명령을 내릴 수 있습니다.
인보커를 통해 로그 작성과 UNDO 기능도 구현가능하지만 여기서는 생략하도록 하겠습니다.
커맨드 패턴은 명령을 내리는 객체(Invoker)와 명령을 수행하는 객체(Receiver)를 분리하여 단일책임원칙(SRP)에 부합합니다. 또한 코드 수정 없이 리시버와 인보커를 추가하여 확장이 가능하므로 개방폐쇄원칙(OCP)에도 부합합니다.
단점은 구조가 복잡하여 디버깅이 어려울 수 있습니다. |