객체지향 프로그래밍을 위한 디자인 패턴중 행위에 관한 패턴을 다룬다.
1. ChainOfResponsibility
2. Command
3. Interpreter
4. Iterator
5. Meditator
6. Memento
7. Observer
8. State
9. Strategy
10. TemplateMethod
11. Visitor
1. ChainOfResponsibility
명령의 처리가 가능한 처리 객체를 찾을때까지 명령의 처리를 위임한다.
명령의 처리를 위한 핸들러들을 만들어 핸들러가 처리할 수 있는 명령이라면 자신이 처리하게 되고, 자신이 처리할 수 없는 명령이라면 다음 핸들러로 명령을 위임하게 한다.

핸들러는 다음 핸들러를 필드로 가지며, 자신이 수행할 수 있는 명령인지 아닌지 판별한다. 판별 후 자신이 이행하지 못하는 명령이라면 다음 핸들러로 명령을 넘기게 된다.
OS(Handler)는 명령을 수행할 수 있다.
리눅스 (ConcreteHandler1)은 next로 맥 (ConcreteHandler2)를 가진다.
맥 (ConcreteHandler2)는 next로 윈도우(ConcreteHandler3)을 가진다.
exe파일에 대한 수행이 명령으로 리눅스(ConcreteHandler1)에게 주어지면
자신이 수행할 수 없으므로 다음 핸들러 (ConcreteHandler2)로 위임하게 된다.
맥 (ConcreteHandler2)또한 명령을 수행할 수 없으므로 다음 핸들러(ConcreteHandler3)으로 위임하게 된다.
윈도우 (ConcreteHandler3)은 명령을 수행할 수 있으므로, 명령을 수행 후 결과를 반환한다.
Handler.java
public abstract class Handler {
public String name;
public Handler next;
public Handler(String name){
this.name = name;
}
public void setNextHandler(Handler next){
this.next = next;
}
public void handlerRequest(boolean bool){
if (handlerCheck(bool)){
System.out.println("handler : " + this.name + "처리");
} else {
System.out.println("다음 핸들러로 넘김");
next.handlerRequest(bool);
}
}
public abstract boolean handlerCheck(boolean bool);
}
Handler는 next(Handler) 필드와 자신이 수행할 수 있는지에 대한 handlerCheck 메소드를 가진다.
ConcreteHandler1.java
public class ConcreteHandler1 extends Handler{
public ConcreteHandler1(String name){
super(name);
}
@Override
public boolean handlerCheck(boolean bool) {
return bool;
}
}
ConcreteHandler1은 들어온 요청이 true인경우 수행할 수 있다.
ConcreteHandler2.java
public class ConcreteHandler2 extends Handler{
public ConcreteHandler2(String name){
super(name);
}
@Override
public boolean handlerCheck(boolean bool) {
return !bool;
}
}
ConcreteHandler2는 들어온 요청이 false인경우 수행할 수 있다.
Demo.java
public class Demo {
public void run(){
Handler trueHandler = new ConcreteHandler1("trueHandler");
Handler falseHandler = new ConcreteHandler2("falseHandler");
falseHandler.setNextHandler(trueHandler);
trueHandler.handlerRequest(true);
falseHandler.handlerRequest(true);
}
}
falseHandler의 경우 다음 핸들러를 trueHandler로 지정한다.
trueHandler에게 true를 요청할경우 해당 핸들러가 요청을 처리하게 되지만,
falseHandler에게 true를 요청할경우 해당 핸들러가 요청을 처리할 수 없으므로, 다음 핸들러로 위임하게 된다.
Output
handler : trueHandler처리
다음 핸들러로 넘김
handler : trueHandler처리
2. Command
명령 요청을 캡슐화 하여 수행하게 될 명령에 대한 의존성을 줄여준다.
커맨드 패턴은 명령을 객체로 캡슐화 하여 특정 명령의 수행을 객체의 전달로써 실행시킨다.

Invoker는 지정할 명령(Command)를 선택하여 수행한다.
Command의 구현체 ConcreteCommand는 실제 작업을 진행할 Receiver에게 어떠한 작업을 수행할지 명령한다.
리모컨(Invoker)에는 TV(Receiver)를 조작하는 버튼(Command)들이 있다.
볼륨 키우기 버튼(ConcreteCommand1)을 누르게 되면 TV(Receiver)에게 볼륨을 키우라는 명령이 전달되게 되고,
전원 버튼(ConcreteCommand2)을 누르게 되면 TV(Receiver)에게 TV를 끄라는 명령이 전달되게 된다.
Invoker.java
public class Invoker {
private Command command;
public void setCommand(Command command){
this.command = command;
}
public void executeCommand(){
command.execute();
}
}
Invoker는 수행할 명령(Command)를 필드로 가지고, 해당 명령을 수행할 수 있다.
Command.java
public interface Command {
public void execute();
}
ConcreteCommand.java
public class ConcreteCommand1 implements Command{
private Receiver receiver;
public ConcreteCommand1(Receiver receiver){
this.receiver = receiver;
}
public void execute(){
this.receiver.action1();
}
}
ConcreteCommand는 실제 작업을 수행할 Receiver를 가지며, 실제 receiver의 수행을 지시한다.
Receiver.java
public class Receiver {
public void action1(){
System.out.println("action1");
}
public void action2(){
System.out.println("action2");
}
}
실제 동작이 정의된다.
Demo.java
public class Demo {
public void run(){
Receiver receiver = new Receiver();
Invoker invoker = new Invoker();
Command command1 = new ConcreteCommand1(receiver);
Command command2 = new ConcreteCommand2(receiver);
Command command3 = new ConcreteCommand3(receiver);
invoker.setCommand(command1);
invoker.executeCommand();
invoker.setCommand(command2);
invoker.executeCommand();
invoker.setCommand(command3);
invoker.executeCommand();
}
}
위에서 언급하지 않았지만
command1은 action1을 수행
command2는 action2를 수행
command3은 action1, action2를 수행하도록 되어있다.
invoker의 커멘드를 지정하고 해당 커멘드를 실행시키게 되면 정의된 receiver의 동작이 일어나게 된다.
Output
action1
action2
action1
action2
3. Interpreter
언어의 구문을 해석하기 위한 패턴
종단 표현(Terminal Expression)과 비종단 표현(Non Terminal Expression)으로 나눠 언어를 해석하는 패턴이다.
쉽게 말해 우리가 쓰는 표현들을 문법으로 정의하고 그 문법을 해석하기 위한 패턴이다.

논리식(Expression)을 해석하기 위한 인터프리터를 만든다.
이 인터프리터는 and연산을 수행할 수 있다.
종단 표현(TerminalExpression)은 자기 자신으로 끝날 수 있는 표현이다.
비종단 표현(NonTerminalExpression)은 다른 표현을 참조하고 있는 표현이다.
and 연산을 수행하는 NonTerminalExpression은 두개의 종단 표현을 참조하여 두 표현간 and 결과를 리턴한다.
Expression.java
public interface Expression {
boolean interpreter(String context);
}
TerminalExpression.java
public class TerminalExpression implements Expression{
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpreter(String context){
if(context.contains(data))
return true;
else
return false;
}
}
종단 표현에 관한 클래스다. context에 데이터가 포함되면 true를 리턴함.
NonTerminalExpression.java
public class NonTerminalExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public NonTerminalExpression(Expression expr1, Expression expr2){
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpreter(String context){
return expr1.interpreter(context) && expr2.interpreter(context);
}
}
비종단 표현에 관한 클래스다. 두개의 표현의 &&연산결과를 리턴한다.
Demo.java
public class Demo {
public void run(){
Expression kim = new TerminalExpression("kim");
Expression lee = new TerminalExpression("lee");
Expression isSame = new NonTerminalExpression(kim, lee);
System.out.println(isSame.interpreter("kim and park"));
System.out.println(isSame.interpreter("kim and lee"));
}
}
두개의 종단표현 "kim", "lee"를 만들어 하나의 NonTerminalExpression으로 만든다.
이 NonTerminalExpression은 "kim"과 "lee"라는 표현이 들어가 있으면 true를 리턴하고, 둘중 하나라도 들어있지 않다면 false를 리턴한다.
첫번째 "kim and park"의 경우 두 종단 표현중 lee 라는 종단 표현을 만족하지 못하므로 false가 되게 된다.
두번째 "kim and lee"의 경우 두 종단 표현 kim, lee를 모두 만족하므로 true가 되게 된다.
Output
false
true
4. Iterator
연속적인 요소들에 대한 접근을 제공한다.
Iterator 객체를 위한 Aggregator객체와 이를 사용한 Iterator 객체로 구현된다.

Aggregate를 구현한 ConcreteAggregate는 아이템 리스트를 가지며, iterator 객체에 필요한 메소드들을 제공해준다.
결과적으로 createIterator()를 통해 ConcreteIterator 객체를 생성해준다.
Iterator를 구현한 ConcreteIterator는 내부적으로 Aggregate를 사용하며, Iterator의 메소드를 구현해 사용한다.
Item.java
public class Item {
private int id;
public Item(int id){
this.id = id;
}
public void getId(){
System.out.println(this.id);
}
}
Aggregate.java
public interface Aggregate {
public abstract Iterator createIterator();
}
Iterator 객체를 리턴해주는 createIterator() 메소드를 가진다.
ConcreteAggregate.java
public class ConcreteAggregate implements Aggregate{
private Item[] iList;
private int top = 0;
public ConcreteAggregate(int size){
iList = new Item[size];
}
public Item getItem(int index){
return iList[index];
}
public int getSize(){
return top;
}
public void addItem(Item item){
if (top < this.iList.length){
this.iList[top] = item;
top++;
} else {
System.out.println("꽉참");
}
}
@Override
public Iterator createIterator(){
return new ConcreteIterator(this);
}
}
실제 Item 리스트와 Iterator 에게 필요한 각종 메소드들을 제공해준다.
ConcreteIterator.java
public class ConcreteIterator implements Iterator<Item> {
private ConcreteAggregate aggregate;
private int top = 0;
public ConcreteIterator(ConcreteAggregate orderList){
this.aggregate = orderList;
}
@Override
public boolean hasNext(){
return top < aggregate.getSize();
}
@Override
public Item next(){
Item order = this.aggregate.getItem(top);
top++;
return order;
}
}
Iterator를 구현한 ConcreteIterator 객체로, hasNext, next 함수를 구현한다.
내부적으로 aggregate를 사용한다.
Demo.java
public class Demo {
public void run(){
ConcreteAggregate orderList = new ConcreteAggregate(5);
Item order1 = new Item(123);
Item order2 = new Item(412);
Item order3 = new Item(555);
orderList.addItem(order1);
orderList.addItem(order2);
orderList.addItem(order3);
Iterator oit = orderList.createIterator();
while(oit.hasNext()){
Item order = (Item) oit.next();
order.getId();
}
}
}
새로운 Aggregate를 만들어 해당 Aggregate로 Iterator를 만들어 사용한다.
Output
123
412
555
5. Meditator
객체간 상호작용을 하나의 객체에 위임하는 방식
객체들간에는 수많은 상호작용들이 일어난다. 5개의 객체가 서로 상호작용을 한다면 이것만 해도 20개의 상호작용이 일어나게 된다.
이러한 수많은 관계들을 하나의 객체로 위임하여 M:N 관계를 M:1 관계로 만드는것이 Meditator 패턴이다.

Colleague들은 서로 통신을 위한 객체다.
Meditator는 이러한 Colleague 객체들을 필드로 가지며, 특정 Colleague가 메시지를 보내게 되면, 자신이 가지고 있는 모든 Colleague로 메시지를 전파시켜 준다.
pub-sub 구조와 같다.
Meditator.java
public interface Meditator {
ArrayList<Colleague> clist = new ArrayList();
public void addColleague(Colleague c);
public void deleteColleague(Colleague c);
public void sendMessage(String msg, Colleague c);
}
ConcreteMeditator.java
public class ConcreteMeditator implements Meditator{
@Override
public void addColleague(Colleague c) {
clist.add(c);
}
@Override
public void deleteColleague(Colleague c) {
clist.remove(c);
}
@Override
public void sendMessage(String msg, Colleague c) {
for (Colleague colleague : clist){
if(colleague != c){
colleague.receiveMsg(msg);
}
}
}
}
Meditator 인터페이스의 add, delete, send 를 구현한다.
sendMessage의 경우 메시지를 보낸 Colleague를 빼고 모두 전파한다.
Colleague.java
public abstract class Colleague {
Meditator meditator;
String name;
public Colleague(Meditator m, String name){
this.meditator = m;
this.name = name;
}
public void sendMsg(String msg){
meditator.sendMessage(msg, this);
}
public abstract void receiveMsg(String msg);
}
Colleague는 Meditator를 필드로 가지며 이 meditator를 통해 메시지를 전파한다.
ConcreteColleague.java
public class ConcreteColleague1 extends Colleague{
public ConcreteColleague1(Meditator m, String name){
super(m, name);
}
@Override
public void receiveMsg(String msg) {
System.out.printf("Colleague1 : %s : msg[%s]\n", this.name, msg);
}
}
실제 메시지를 수신하는 receiveMsg를 구현한다.
Demo.java
public class Demo {
public void run(){
Meditator m = new ConcreteMeditator();
Colleague c1 = new ConcreteColleague1(m, "c1");
Colleague c2 = new ConcreteColleague2(m, "c2");
Colleague c3 = new ConcreteColleague3(m, "c3");
m.addColleague(c1);
m.addColleague(c2);
m.addColleague(c3);
c1.sendMsg("hello~");
}
}
c1, c2, c3 모두 Meditator에 등록한다. addColleague()
이후 c1.sendMsg()를 통해 메시지를 Meditator에게 전달하게 되면, 모든 Colleague로 메시지가 전파되게 된다.
Output
Colleague2 : c2 : msg[hello~]
Colleague3 : c3 : msg[hello~]
6. Memento
상태를 저장하여 언제든지 해당 상태로 돌아갈(rollback) 수 있게 한다.
Memento는 특정 객체가 작업을 수행하다 현재 상태를 저장해야 할때, 현재 상태를 저장하는 Memento객체를 만들어 해당 객체를 저장(commit)함으로써 나중에 해당 상태로 되돌아갈 수 있는(rollback) 패턴이다.

Originator는 작업을 수행하는 클래스고, 이 클래스에서 상태를 저장해야 할때 Memento를 만들어 CareTaker에게 저장하게 된다.
이후 저장했던 특정 시점으로 돌아가고자 할때는 CareTaker에게서 해당 상태를 가져와 rollback하게 된다.
Originator.java
public class Originator {
private String state = "";
public void setState(String state){
this.state = state;
}
public String getState(){
return this.state;
}
public Memento commit(){
return new Memento(this.state);
}
public void rollback(Memento m){
this.state = m.getState();
}
}
commit을 통해 현재 상태를 가지는 Memento를 리턴하고,
rollback을 통해 Memento의 상태를 현재 상태로 치환한다.
Memento.java
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState(){
return this.state;
}
}
Originator와 똑같은 상태를 가진다.
CareTaker.java
public class CareTaker {
private ArrayList<Memento> mlist = new ArrayList();
public void pushMemento(Memento m){
this.mlist.add(m);
}
public Memento popMemento(){
return this.mlist.remove(this.mlist.size() - 1);
}
}
이 예시에서는 CareTaker를 stack으로 구현하였으며, Memento의 push, pop연산을 제공한다.
Demo.java
public class Demo {
public void run(){
Originator o = new Originator();
CareTaker c = new CareTaker();
o.setState("state1");
c.pushMemento(o.commit());
System.out.println(o.getState());
o.setState("state2");
System.out.println(o.getState());
o.rollback(c.popMemento());
System.out.println(o.getState());
}
}
Originator의 경우 state1의 상태를 Memento로 저장한다.
이후 state2로 바꾸어 현재 상태를 확인하고,
rollback을 통해 저장된 state1의 Memento를 자신의 상태로 적용한다.
Output
state1
state2
state1