행동패턴
클라스나 객체들이 서로 상호작용하는 방법이나 책임 분배 방법을 정의하는 패턴이다.
책임 연쇄(Chain of Responsibility)
요청을 처리할 수 있는 객체가 둘 이상 존재하여 한 객체가 처리하지 못하면 다음 객체로 넘어가는 형태의 패턴이다. 클라이언트 요청에 대한 처리를 여러 개의 객체를 나누고, 이들을 사슬(Chain)처럼 연결해 집합 안에서 연쇄적으로 처리한다.
처리 객체들을 핸들러(Handler)라고 하는데, 요청을 받으면 각 핸들러는 요청을 처리할 수 있는지 판단하고, 처리할 수 없으면 다음 핸들러로 처리에 대한 책임을 전가한다. 즉, 책임 연쇄는 요청에 대한 책임을 다른 객체에 떠넘긴다는 의미이다.
사용
- 특정 요청을 2개 이상의 여러 객체에서 판별하고 처리해야할 때
- 특정 순서로 여러 핸들러를 실행해야 하는 경우
- 요청을 처리할 수 있는 객체 집합이 동적으로 정의되어야 할 때 (체인 연결을 런타임에서 동적으로 설정한다.)
장/단점
장점
각각의 체인은 자신이 해야하는 일만 알기 때문에 새로운 요청을 처리할 객체를 생성하는 것이 편리해진다.
클라이언트 코드를 변경하지 않고 핸들러를 체인에 동적으로 추가하거나, 처리 순서를 변경 및 삭제할 수 있어 수정이 유연해진다.
요청의 호출자(invoker)와 수신자(reciever)를 분리시킬 수 있다.
👉 호출자와 수신자 사이의 결합도를 낮춘다.
단점
- 실행 시 코드의 흐름이 많아져서 디버깅이 어려워진다.
- 처리 객체 집합 내부에서 무한 사이클이 발생할 수 있다.
- 요청이 반드시 수행된다는 보장이 없다. (체인 끝까지 갔는데도 처리되지 않을 수 있다.)
- 책임 연쇄로 인해 처리 지연 문제가 발생할 수 있다. 하지만 이는 트레이드 오프한 것이므로, 요청과 처리에 대한 관계가 고정적이고 속도가 중요하면 책임 연쇄 패턴 사용을 유의하여야 한다.
반복자(이터레이터, Iterator)
일련의 데이터 집합에 대해 순차적인 접근 즉, 순회를 지원하는 패턴이다.
- 데이터 집합이란 객체들을 그룹으로 묶어 자료의 구조를 취하는 컬렉션을 말한다.
사용
- 컬렉션에 상관없이 객체 접근 순회 방식을 통일하고자 할 때
- 컬렉션을 순회하는 다양한 방법을 지원하고 싶을 때
- 컬렉션의 복잡한 내부 구조를 클라이언트로부터 숨기고 싶은 경우 (편의, 보안)
- 클라이언트가 데이터 저장 컬렉션 종류를 변경할 가능성이 있을 때
- 클라이언트가 컬렉션 내부 표현 방식을 알고있다면, 표현 방식이 달라졌을 때 클라이언트 코드도 변경되어야하는 문제가 생긴다.
장/단점
장점
- 여러 형태의 컬렉션에 대해 동일한 순회 방식을 제공할 수 있다.
- 컬렉션의 내부 구조 및 순회 방식을 알지 않아도 된다.
- 컬렉션 구현과 접근하는 처리 부분을 반복자 객체로 분리해서 결합도를 줄일 수 있다.
- 단일 책임 원칙(SRP) 준수 : 순회 알고리즘을 별도의 반복자 객체에 추출하고 각 클래스로부터 책임을 분리하였다.
- 개방 폐쇄 원칙(OCP) 준수 : 데이터 저장 컬렉션 종류가 변경되어도 클라이언트 구현 코드는 변경되지 않으므로 수정에는 닫혀있게 된다.
단점
- 클래스가 늘어나고 복잡도가 증가한다.
- 앱에서 간단한 컬렉션만 사용할 때 이 패턴을 적용하는 경우 복잡도만 증가할수 있으므로, 반복자 객체를 분리하는 것이 유용한 상황인지 판단할 필요가 있다.
- 구현 방법에 따라 캡슐화를 위배할 가능성이 있다.
방문자(비지터, Visitor)
각 클래스들의 데이터 구조에서 처리 기능을 분리하여 별도의 클래스로 구성하는 패턴이다.
데이터 구조 안을 돌아다니는 주체인 방문자 객체를 넘기고, 대상 클래스는 방문자 객체의 메서드를 호출하여 정보를 제공한다.
사용
- 상속 없이 클래스에 메소드를 효과적으로 추가하기 위해 사용
- 이질적인 클래스들로 이루어진 객체들의 정보를 하나의 객체로 알아내고 싶을 때
장/단점
장점
- 드물게 사용되는 연산을 외부에 정의할 수 있어서 클래스가 작아진다.
- Visitor는 원소들을 방문하면서 상태를 축적할 수 있다.
단점
- 합성 객체의 내부 구조가 Visitor에 열리게 되고, 이를 통해 캡슐화를 위반하게 된다.
중재자(Mediator)
분산된 다수의 객체의 관계를 하나의 객체로 정리하는 패턴이다. 다른 동료 객체에 직접 접근해서 호출하지 않고 중재자를 의존해서 다른 동료 객체를 호출한다.
사용
- 복잡한 통신과 제어를 한 곳에서 집중하여 처리하도록 할 때 사용한다.
장/단점
장점
- 서로 의존적인 M:N 관계를 가진 객체들을 느슨한 1:1 관계로 변경한다.
- 객체의 강력한 구조적 결함 문제점을 해결한다.
- 동료 객체끼리 정보를 직접 주고받지 않도록 통신 경로를 제한한다.
단점
하나의 객체 요청에 대해 모든 객체로 통보를 처리해야 하므로 경로의 수가 증가한다.
👉 경로의 수가 증가함에 따라 성능이 저하되지 않도록 신경써서 구성해야 한다.
템플릿 메소드(Template Method)
여러 클래스에서 공통으로 사용하는 메서드를 템플릿화하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴이다.
변하지 않는 기능(템플릿)은 상위 클래스에 만들어두고, 자주 변경되며 확장할 기능은 하위클래스에서 만들도록 한다. 이를 통해 상위의 메소드 실행 동작 순서는 고정하면서 세부 실행 내용은 다양화 될 수 있는 경우에 사용된다.
상속이라는 기술을 극대화하여 알고리즘의 뼈대를 맞추는 것에 초점을 둔다.
사용
- 클라이언트가 알고리즘의 특정 단계만 확장하고, 전체 알고리즘이나 해당 구조는 확장하지 않도록 할 때
- 동일한 기능은 상위 클래스에서 정의하면서 확장, 변화가 필요한 부분만 하위 클래스에서 구현할 때
장/단점
장점
- 클라이언트가 알고리즘의 특정 부분만 재정의하도록 하여, 알고리즘의 다른 부분에 발생하는 변경 사항으로부터 영향을 덜 받도록 한다.
- 상위 추상클래스로 로직을 공통화시켜서 코드의 중복을 줄일 수 있다.
- 서브 클래스의 역할을 줄이고, 핵심 로직을 상위 클래스에서 관리함으로써 관리가 용이해진다.
단점
- 제공된 골격에 의해 유연성이 제한될 수 있다.
- 알고리즘 구조가 복잡할 수록 템플릿 로직 형태를 유지하기가 어려워진다.
- 추상 메소드가 많아지면서 클래스의 생성 및 관리가 어려워질 수 있다.
- 상위 클래스에서 선언된 추상 메소드를 하위 클래스에서 구현할 때, 그 메소드가 어느 타이밍에서 호출되는지 클래스 로직을 이해할 필요가 있다.
- 로직에 변화가 생겨 상위 클래스를 수정할 때, 모든 하위 클래스의 수정이 필요할 수도 있다.
- 하위 클래스를 통해 기본 단계 구현을 억제하여 리스코프 치환 원칙을 위반할 가능성이 있다.
전략(Strategy)
실행(런타임) 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있는 패턴이다.
알고리즘 변경 시 조건문을 사용하지 않고, 알고리즘 캡슐화를 통해 원하는 알고리즘으로 교체한다.
사용
- 알고리즘의 동작이 런타임에 실시간으로 교체되어야할 때 사용한다.
- 전략 알고리즘의 여러 버전 또는 변형이 필요할 때 클래스화를 통해 관리한다.
- 알고리즘 코드가 노출되면 안되는 데이터에 접근할 때 캡슐화를 위해 사용한다.
장/단점
장점
- 런타임 시 객체 내부에서 사용되는 알고리즘을 바꿀 수 있다.
- 알고리즘을 사용하는 코드로부터 세부 내용을 분리할 수 있다.
- 개방 폐쇄 원칙(OCP) 준수 : 기존 컨텍스트를 변경하지 않고 새로운 전략을 도입한다.
단점
- 알고리즘이 많아질 수록 관리해야 할 객체의 수가 많아진다. (복잡도 증가)
- 만약 앱에 알고리즘이 많지 않고, 자주 변경되지 않는다면 새로운 클래스와 인터페이스를 만들어 프로그램을 복잡하게 만들 이유가 없다.
- 적절한 전략을 선택하기 위해 전략 간의 차이점을 파악하고 있어야 한다.
템플릿 메서드와 비교
유사점
- 알고리즘을 런타임에서 바꾸면서 적용한다.
- 개방 폐쇄 원칙을 준수 : 코드를 변경하지 않고 소프트웨어 모듈을 쉽게 확장할 수 있다.
차이점
- 전략 패턴은 합성(composition)을 통해 구현하고, 템플릿 메서드 패턴은 상속(inheritance)를 통해 구현한다.그래서 전략 패턴은 클라이언트와 객체 사이에 결합도가 낮은 반면, 템플릿 메서드 패턴은 더 밀접하게 결합된다.
- 전략 패턴에서는 대부분 인터페이스를 사용하지만, 템플릿 메서드 패턴은 주로 추상 클래스나 구체적인 클래스를 사용한다.
- 전략 패턴에서는 전체 전략 알고리즘을 변경할 수 있지만, 템플릿 메서드 패턴에서는 알고리즘의 일부만 변경되고 나머지는 변경되지 않은 상태로 유지된다. (템플릿에 종속)
👉 따라서 단일 상속만 가능한 자바에서는 상속을 사용하는 템플릿 메서드 패턴보다는, 구현(implements)할 수 있는 전략 패턴이 더 많이 사용된다.
상태(State)
조건에 따른 동작을 제어문을 사용하지 않고 상태를 객체화하여 동작을 분리한다. (객체 상태를 클래스로 표현한 패턴)
상태를 클래스로 표현하면 클래스를 교체해서 상태의 변화를 표현할 수 있다.
- 객체 내부 상태 변경에 따라서 객체의 행동을 상태에 특화된 행동들로 분리할 수 있다. (조건문의 각 분기를 별도의 클래스에 넣는 것이 상태 패턴의 핵심)
- 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.
- 상태 클래스는 싱글톤 클래스로 구성한다. 상태는 그 객체의 현재 폼을 나타내는 것이기 때문에 대부분의 상황에서 유일하게 있어야 한다.
사용
- 객체의 행동(메서드)가 상태에 따라 다른 동작을 할 때
- 상태 및 전환에 따라 대규모 조건 분기 코드나 중복 코드가 많을 경우
- 런타임 시 객체의 상태를 유동적으로 변경해야 할 때
장/단점
장점
- 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있다.
- 상태와 관련된 모든 동작을 각각의 상태 클래스에 분산시킴으로써 코드 복잡도를 줄일 수 있다.
- 하나의 상태 객체만 사용하여 상태 변경을 하므로 일관성 없는 상태 주입을 방지하는데 도움이 된다.
- 단일 책임 원칙(DIP) 준수 : 특정 상태와 관련된 코드를 별도의 클래스로 구성한다.
- 개방 폐쇄 원칙(OCP) 준수 : 기존 상태 클래스나 컨텍스트를 변경하지 않고 새 상태를 도입할 수 있다.
단점
- 상태 별로 클래스를 생성하므로 관리해야 할 클래스 수가 증가한다.
- 상태 클래스 수가 많고 상태 규칙이 자주 변경될 경우, Context의 상태 변경 코드가 복잡해질 수 있다.
- 객체에 적용할 상태가 거의 없거나, 상태 변경이 거의 이루어지지 않는 경우 패턴을 적용하는 것이 더 비효율적일 수 있다.
메멘토(Memento)
특정 시점에서의 객체 내부 상태를 객체화함으로써 이후 요청에 따라 객체를 할당 시점으로 돌릴 수 있는 패턴이다.
옵저버(Observer)
한 객체의 상태가 변화하면 객체에 상속되어 있는 다른 객체들에게 변화된 상태를 전달하는 패턴이다.
인터프리터(Interpreter)
언어에 문법 표현을 정의하는 패턴이다.
커맨드(Command)
요청을 객체의 형태로 캡슐화하여 재이용하거나 취소할 수 있도록 요청에 필요한 정보를 저장하거나 로그에 남기는 패턴이다.
출처
- inpa.tistory.com/entry/GOF-💠-Chain-Of-Responsibility-패턴-완벽-마스터하기
- https://johngrib.github.io/wiki/pattern/visitor/
- https://hirlawldo.tistory.com/m/180