책임 연쇄 패턴은 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다. 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지를 결정합니다.
책임 연쇄 패턴이란?
책임 연쇄 패턴은 특정 행동들을 핸들러라는 독립 실행형 객체들로 변환합니다. 당신의 앱의 경우 각 검사는 검사를 수행하는 단일 메서드가 있는 자체 클래스로 추출되어야 합니다. 이제 요청은 데이터와 함께 이 메서드에 인수로 전달됩니다.
핸들러는 모든 구상 핸들러에 공통적인 인터페이스를 선언합니다. 일반적으로 여기에는 요청을 처리하기 위한 단일 메서드만 포함되지만 때로는 체인의 다음 핸들러를 세팅하기 위한 다른 메서드가 있을 수도 있습니다.
기초 핸들러는 선택적 클래스이며 여기에 모든 핸들러 클래스들에 공통적인 상용구 코드를 넣을 수 있습니다.
일반적으로 이 클래스는 다음 핸들러에 대한 참조를 저장하기 위한 필드를 정의합니다. 클라이언트들은 핸들러를 이전 핸들러의 생성자 또는 세터(setter)에 해당 핸들러를 전달하여 체인을 구축할 수 있습니다. 또 클래스는 디폴트 핸들러 행동을 구현할 수도 있습니다. 즉, 다음 핸들러의 존재 여부를 확인한 후 다음 핸들러로 실행을 넘길 수 있습니다.
구상 핸들러들에는 요청을 처리하기 위한 실제 코드가 포함되어 있습니다. 각 핸들러는 요청을 받으면 이 요청을 처리할지와 함께 체인을 따라 전달할지를 결정해야 합니다.
핸들러들은 일반적으로 자체 포함형이고 불변하며, 생성자를 통해 필요한 모든 데이터를 한 번만 받습니다.
클라이언트는 앱의 논리에 따라 체인들을 한 번만 구성하거나 동적으로 구성할 수 있습니다. 참고로 요청은 체인의 모든 핸들러에 보낼 수 있으며, 꼭 첫 번째 핸들러일 필요는 없습니다.
publicstaticvoidmain(String[] args){ Chain c1 = new NegativeProcess(); Chain c2 = new ZeroProcess(); Chain c3 = new PositiveProcess();
c1.setNext(c2); c2.setNext(c3);
c1.process(new Number(1111)); }
장단점
장점
요청의 처리 순서를 제어할 수 있습니다.
단일 책임 원칙. 당신은 작업을 호출하는 클래스들을 작업을 수행하는 클래스들과 분리할 수 있습니다.
개방/폐쇄 원칙. 기존 클라이언트 코드를 손상하지 않고 앱에 새 핸들러들을 도입할 수 있습니다.
단점
일부 요청들은 처리되지 않을 수 있습니다.
커맨드 패턴
사용 상황 요약
커맨드는 요청을 요청에 대한 모든 정보가 포함된 독립실행형 객체로 변환하는 행동 디자인 패턴입니다. 이 변환은 다양한 요청들이 있는 메서드들을 인수화 할 수 있도록 하며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 또 실행 취소할 수 있는 작업을 지원할 수 있도록 합니다.
즉, 호출자와 수신자를 분리한다. -> Button을 호출했지만, 어떤 기능이든 실행가능한 버튼 생성가능(호출자), 실제로 로직수행하는 객체(수신자)
커맨드 패턴이란?
여러 기능을 구현한 버튼을 만들때, 자식들을 여러개 만드는 것이 아니라, 버튼 객체에 execute() 메서드를 가진 인터페이스를 의존하게한다. 이제 기능에 따라 execute()를 구현한 구현체만 골라서 실행하면된다.
발송자 클래스(invoker라고도 함)는 요청들을 시작하는 역할을 합니다. 이 클래스에는 커맨드 객체에 대한 참조를 저장하기 위한 필드가 있어야 합니다. 발송자는 요청을 수신자에게 직접 보내는 대신 해당 커맨드를 작동시킵니다. 참고로 발송자는 커맨드 객체를 생성할 책임이 없으며 일반적으로 생성자를 통해 클라이언트로부터 미리 생성된 커맨드를 받습니다.
커맨드 인터페이스는 일반적으로 커맨드를 실행하기 위한 단일 메서드만을 선언합니다.
구상 커맨드들은 다양한 유형의 요청을 구현합니다. 구상 커맨드는 자체적으로 작업을 수행해서는 안 되며, 대신 비즈니스 논리 객체 중 하나에 호출을 전달해야 합니다. 그러나 코드를 단순화하기 위해 이러한 클래스들은 병합될 수 있습니다.
수신 객체에서 메서드를 실행하는 데 필요한 매개 변수들은 구상 커맨드의 필드들로 선언할 수 있습니다. 생성자를 통해서만 이러한 필드들의 초기화를 허용함으로써 커맨드 객체들을 불변으로 만들 수 있습니다.
수신자 클래스에는 일부 비즈니스 로직이 포함되어 있습니다. 거의 모든 객체는 수신자 역할을 할 수 있습니다. 대부분의 커맨드들은 요청이 수신자에게 전달되는 방법에 대한 세부 정보만 처리하는 반면 수신자 자체는 실제 작업을 수행합니다.
클라이언트는 구상 커맨드 객체들을 만들고 설정합니다. 클라이언트는 수신자 인스턴스를 포함한 모든 요청 매개변수들을 커맨드의 생성자로 전달해야 하며 그렇게 만들어진 커맨드는 하나 또는 여러 발송자와 연관될 수 있습니다.
publicclassPattern2Main{ publicstaticvoidmain(String[] args){ Lamp lamp = new Lamp(); Command lampOnCommand = new LampOnCommand(lamp); Alarm alarm = new Alarm(); Command alarmStartCommand = new AlarmStartCommand(alarm);
Button button1 = new Button(lampOnCommand); // 램프 켜는 Command 설정 button1.pressed(); // 램프 켜는 기능 수행
Button button2 = new Button(alarmStartCommand); // 알람 울리는 Command 설정 button2.pressed(); // 알람 울리는 기능 수행 button2.setCommand(lampOnCommand); // 다시 램프 켜는 Command로 설정 button2.pressed(); // 램프 켜는 기능 수행 } }
장단점
장점
단일 책임 원칙. 작업을 호출하는 클래스들을 이러한 작업을 수행하는 클래스들로부터 분리할 수 있습니다.
개방/폐쇄 원칙. 기존 클라이언트 코드를 손상하지 않고 앱에 새 커맨드들을 도입할 수 있습니다.
실행 취소/다시 실행을 구현할 수 있습니다.
작업들의 지연된 실행을 구현할 수 있습니다.
간단한 커맨드들의 집합을 복잡한 커맨드로 조합할 수 있습니다.
단점
발송자와 수신자 사이에 완전히 새로운 레이어를 도입하기 때문에 코드가 더 복잡해질 수 있습니다.
반복자(중요X)
사용 상황 요약
반복자는 컬렉션의 요소들의 기본 표현(리스트, 스택, 트리 등)을 노출하지 않고 그들을 하나씩 순회할 수 있도록 하는 행동 디자인 패턴입니다.
접근기능과 자료구조를 분리시켜서 객체화합니다. 서로 다른 구조를 가지고 있는 저장 객체에 대해서 접근하기 위해서 interface를 통일시키고 싶을 때 사용하는 패턴입니다.
반복자 패턴 이란?
반복자 인터페이스는 컬렉션의 순회에 필요한 작업들(예: 다음 요소 가져오기, 현재 위치 가져오기, 반복자 다시 시작 등)을 선언합니다.
구상 반복자들은 컬렉션 순회를 위한 특정 알고리즘들을 구현합니다. 반복자 객체는 순회의 진행 상황을 자체적으로 추적해야 합니다. 이는 여러 반복자들이 같은 컬렉션을 서로 독립적으로 순회할 수 있도록 합니다.
컬렉션 인터페이스는 컬렉션과 호환되는 반복자들을 가져오기 위한 하나 이상의 메서드들을 선언합니다. 참고로 메서드들의 반환 유형은 반복자 인터페이스의 유형으로 선언되어야 합니다. 그래야 구상 컬렉션들이 다양한 유형의 반복자들을 반환할 수 있기 때문입니다.
구상 컬렉션들은 클라이언트가 요청할 때마다 특정 구상 반복자 클래스의 새 인스턴스들을 반환합니다. 당신은 컬렉션의 나머지 코드가 어디에 있는지 궁금하실 수도 있습니다. 걱정하지 마세요, 같은 클래스에 있을 것입니다. 나머지 코드가 어디에 있는지와 같은 세부 사항들은 실제 패턴에 중요하지 않으므로 생략하기로 했습니다.
클라이언트는 반복자들과 컬렉션들의 인터페이스를 통해 그들과 함께 작동합니다. 이렇게 하면 클라이언트가 구상 클래스들에 결합하지 않으므로 같은 클라이언트 코드로 다양한 컬렉션들과 반복자들을 사용할 수 있도록 합니다.
일반적으로 클라이언트들은 자체적으로 반복자들을 생성하지 않고 대신 컬렉션들에서 가져옵니다. 그러나 어떤 경우에는 (예를 들어 클라이언트가 자체 특수 반복자를 정의할 때) 클라이언트가 반복자를 직접 만들 수 있습니다.
예시
BookShelf 예제
책꽂이 안에 책을 꽂고, 다시 책을 하나씩 확인하는 예제를 작성한다.
1) Book : 한권의 책에 대한 정보를 가지고 있는 클래스
1 2 3 4 5 6 7 8 9 10 11
publicclassBook{ private String name;
publicBook(String name){ this.name = name; }
public String getName(){ return name; } }
2) Aggregate : 집합체를 의미하는 인터페이스
Aggregate는 Iterator 역할을 만들어내는 인터페이스를 결정한다.
1 2 3 4
publicinterfaceAggregate{
publicabstract Iterator createIterator(); }
3) BookShelf : 책을 보관하는 책꽂이 역할을 하는 클래스
BookShelf는 이러한 Aggregate의 구현체이다. 실제로 책꽂이 안을 돌아다닐 Iterator를 생성하고 책꽂이를 관리하는 역할을 한다.
System.out.println("현재 꽂혀있는 책 : " + bookShelf.getLength() + "권");
Iterator it = bookShelf.createIterator(); while (it.hasNext()) { Book book = (Book) it.next(); System.out.println(book.getName()); } } }
BookShelfIterator는 책꽂이에서 책을 한권씩 뽑아오는 역할을 한다.
검색할 책이 존재하는 동안 while문이 수행되며, it.next()에 의해 한권씩 책을 꺼내 이름을 출력한다.
Iterator 패턴을 적용하면 어떤 장점이 있을까?
for문으로 동일한 표현이 가능한데 왜 굳이 코드를 늘려가며 Iterator 패턴을 사용해야 하는지 의문이 들 수 있다.
Iterator 패턴을 사용하는 가장 큰 이유는 하나씩 꺼내서 처리하는 과정을 구현과 분리할 수 있기 때문이다.
다음의 코드를 살펴보자.
1) Iterator 패턴을 사용했을 때
1 2 3 4
while (it.hasNext()) { Book book = (Book) it.next(); System.out.println(book.getName()); }
2) for문을 사용했을 때
1 2 3
for (int i = 0; i < bookShelf.getLength(); i++) { System.out.println(bookShelf.getBook(i).getName()); }
이 두가지 코드는 실행 시 Book 객체의 내용을 하나씩 꺼내서 보여주는 동일한 결과를 보여준다.
하지만 for문을 사용할 때와 달리 Iterator 패턴을 사용할 때는 어디까지나 Iterator의 메서드를 사용할 뿐 BookShelf의 구현에서 사용되고 있는 메서드는 호출되지 않고있다.
즉, 1번 방식은 BookShelf의 구현에 의존하지 않는다.
BookShelf에 무언가 수정사항이 생기더라도 BookShelf가 올바른 Iterator를 반환하기만 한다면 while문이 사용되는 Main은 수정할 필요가 없다. 하나의 클래스를 수정하더라도 다른 클래스에 큰 영향 없이 작은 수정 만으로도 끝낼 수 있다는 것이다.
장단점
장점
단일 책임 원칙. 부피가 큰 순회 알고리즘들을 별도의 클래스들로 추출하여 클라이언트 코드와 컬렉션들을 정돈할 수 있습니다.
개방/폐쇄 원칙. 새로운 유형의 컬렉션들과 반복자들을 구현할 수 있으며 이들을 아무것도 훼손하지 않은 체 기존의 코드에 전달할 수 있습니다.
당신은 이제 같은 컬렉션을 병렬로 순회할 수 있습니다. 왜냐하면 각 반복자 객체에는 자신의 고유한 순회 상태가 포함되어 있기 때문입니다.
같은 이유로 당신은 순회를 지연하고 필요할 때 계속할 수 있습니다.
단점
당신의 앱이 단순한 컬렉션들과만 작동하는 경우 반복자 패턴을 적용하는 것은 과도할 수 있습니다.
반복자를 사용하는 것은 일부 특수 컬렉션들의 요소들을 직접 탐색하는 것보다 덜 효율적일 수 있습니다
중재자 패턴
사용 상황 요약
중재자는 객체 간의 혼란스러운 의존 관계들을 줄일 수 있는 행동 디자인 패턴입니다. 이 패턴은 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 합니다.
중재자 패턴이란?
컴포넌트들은 어떤 비즈니스 로직을 포함한 다양한 클래스들입니다. 각 컴포넌트에는 중재자에 대한 참조가 있는데, 이 중재자는 중재자 인터페이스의 유형으로 선언됩니다. 컴포넌트는 중재자의 실제 클래스를 인식하지 못하므로 컴포넌트를 다른 중재자에 연결하여 다른 프로그램에서 재사용할 수 있습니다.
중재자 인터페이스는 일반적으로 단일 알림 메서드만을 포함하는 컴포넌트들과의 통신 메서드들을 선언합니다. 컴포넌트들은 자체 객체들을 포함하여 모든 콘텍스트를 이 메서드의 인수로 전달할 수 있지만 이는 수신자 컴포넌트와 발송자 클래스 간의 결합이 발생하지 않는 방식으로만 가능합니다.
구상 중재자들은 다양한 컴포넌트 간의 관계를 캡슐화합니다. 구상 중재자들은 자신이 관리하는 모든 컴포넌트에 대한 참조를 유지하고 때로는 그들의 수명 주기를 관리하기도 합니다.
컴포넌트들은 다른 컴포넌트들을 인식하지 않아야 합니다. 컴포넌트 내에서 또는 컴포넌트에 중요한 일이 발생하면, 컴포넌트는 이를 중재자에게만 알려야 합니다. 중재자는 알림을 받으면 발송자를 쉽게 식별할 수 있으며, 이는 응답으로 어떤 컴포넌트가 작동되어야 하는지 결정하기에 충분할 수 있습니다.
컴포넌트의 관점에서는 모든 것들이 블랙박스들(기능은 알지만, 작동 원리를 이해할 수 없는 복잡한 기계나 시스템)처럼 보입니다. 발송자는 누가 요청을 처리할지 모르고, 수신자는 누가 처음에 요청을 보냈는지를 모릅니다.
메멘토는 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있게 해주는 행동 디자인 패턴입니다.
메멘토 패턴 이란?
오리지네이터 클래스는 자신의 상태에 대한 스냅샷들을 생성할 수 있으며, 필요시 스냅샷에서 자신의 상태를 복원할 수도 있습니다.
메멘토는 오리지네이터의 상태의 스냅샷 역할을 하는 값 객체입니다. 관행적으로 메멘토는 불변으로 만든 후 생성자를 통해 데이터를 한 번만 전달합니다.
케어테이커는 ‘언제’ 그리고 ‘왜’ 오리지네이터의 상태를 캡처해야 하는지 뿐만 아니라 상태가 복원돼야 하는 시기도 알고 있습니다.
케어테이커는 메멘토들의 스택을 저장하여 오리지네이터의 기록을 추적할 수 있습니다. 오리지네이터가 과거로 돌아가야 할 때 케어테이커는 맨 위의 메멘토를 스택에서 가져온 후 오리지네이터의 복원 메서드에 전달합니다.
이 구현에서 메멘토 클래스는 오리지네이터 내부에 중첩됩니다. 이것은 오리지네이터가 메멘토의 필드들과 메서드들이 비공개로 선언된 경우에도 접근할 수 있도록 합니다. 반면에, 케어테이커는 메멘토의 필드들과 메서드들에 매우 제한된 접근 권한을 가지므로 메멘토들을 스택에 저장할 수는 있지만 그들의 상태를 변조할 수는 없습니다.
예시
Originator.class
Originator : 현재 State를 가지고, Memento 객체와 Memento 객체 상태를 얻게 한다
@Override publicvoidupdate(String eventType, File file){ System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName()); } }
publicclassDemo{ publicstaticvoidmain(String[] args){ Editor editor = new Editor(); editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt")); editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
>>> Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt Email to admin@example.com: Someone has performed save operation with the following file: test.txt