반복적으로 사용되는 객체들의 상호작용을 패턴화한 것으로, 쿨래스나 객체들이 상호작용하는 방법과 책임을 분산하는 방법을 제공한다.
행위 패턴은 행위 관련 패턴을 사용하여 독립적으로 일을 처리하고자 할때 사용
종료
Strategy
Template Method
Observer
Chain of responsibility
Interpreter
Iterator
Visitor
Command
Mediator
State
Memento
Strategy Pattern
정책이나 알고리즘을 교체하여 사용한다.
보통 실무에서 많이 사용되는 패턴이다.
같은 메서드를 경우에 따라 다르게 구현해서 사용하는 방식이다.
다양한 알고리즘이 존재하면 이들 각각을 하나의 클래스로 캡슐화하여 알고리즘의 대체가 가능하도록 한다.
class diagram
Strategy: 정책이 수행해야 하는 기능들을 인터페이스로 선언한다.
ConcreteStrategy: Strategy에 선언된 여러 기능들을 구현한다.
Context
어떤 ConcreteStrategy 가 수행 될 것인지에 따라 정책을 선택한다.
Strategy에 선언된 메서드 기반으로 접근한다.
Strategy 클래스와 Context 클래스는 선택한 알고리즘이 동작하도록 협력한다.
예제
public interface EmailProvider {
String getEmail(User user);
}
위의 interface 는 Abstract 메소드 1개만을 가지고 있기 때문에 Functional Interface 이다.
다음으로는 위의 인터페이스를 구현한 2개의 Provider 를 구현한다.
public class VerifyYourEmailAddressEmailProvider implements EmailProvider {
@Override
public String getEmail(User user) {
return "'Verify Your Email Address' email for " + user.getName();
}
}
public class MakeMoreFriendsEmailProvider implements EmailProvider {
@Override
public String getEmail(User user) {
return "'Make More Friends' email for " + user.getName();
}
}
이제 아래와 같이 Strategy Pattern 을 적용할 수 있다. (User 와 관련된 코드는 생략한다.)
emailSender.setEmailProvider(verifyYourEmailAddressEmailProvider); // 전략 주입
users.stream()
.filter(user -> !user.isVerified())
.forEach(emailSender::sendEmail);
emailSender.setEmailProvider(makeMoreFriendsEmailProvider); // 전략 변경
users.stream()
.filter(User::isVerified)
.filter(user -> user.getFriendUserIds().size() <= 5)
.forEach(emailSender::sendEmail);
전략이 변경됨에 따라 sendEmail 메소드의 결과 값이 달라진다.
하지만 이렇게 전략을 만들때마다 클래스를 만들어 줘야할까? -> EmailProvider 가 Functional Interface 이기 때문에 클래스를 따로 구현하지 않고 아래와 같이 람다식으로 대체할 수 있다.
Template Method Pattern
상위 클래스에서 전체적인 흐름을 구현하고 상세한 처리는 하위 클래스에게 위임한다.
템플릿 메소드는 코드 재사용을 위한 기본 기술이다.
프레임워크에서 가장 많이 사용되는 패턴 중 하나이다.
class diagram
AbstractClass: 서브 클래스들이 반드시 구현해야 하는 알고리즘 처리 단계 내의 기본 오퍼레이션이 무엇인지를 정의한다. 서브 클래스에서 이들 오퍼레이션들을 구현한다.
ConcreteClass: 상위 클래스에서 선언된 추상 메서드를 구현하거나 이미 구현된 메서드를 재정의한다
예제
public abstract class PlayerLevel {
public abstract void run();
public abstract void jump();
public abstract void turn();
public abstract void showLevelMessage();
final public void go(int count)
{
run();
for(int i=0; i<count; i++){
jump();
}
turn();
}
public void fly() {}
}
template method 인 go 메서드는 하위 클래스에서 재정의 할 수 없도록 final 로 선언한다.
fly() 메서드는 {} 가 있기 때문에 abstract 메소드가 아니다.
그렇기 때문에 하위 클래스에서 재정의 할 수 있고, 이러한 메서드는 훅메서드라 한다.
훅 메서드는 하위 클래스에서 구현해도 되고 안해도 되는 경우에 사용된다.
public class BeginnerLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("천천히 달립니다.");
}
@Override
public void jump() {
System.out.println("Jump 할 줄 모르지롱.");
}
@Override
public void turn() {
System.out.println("Turn 할 줄 모르지롱.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 초보자 레벨 입니다. *****");
}
}
public class AdvancedLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("높이 jump 합니다.");
}
@Override
public void turn() {
System.out.println("Turn 할 줄 모르지롱.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 중급자 레벨 입니다. *****");
}
}
public class SuperLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("엄청 빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("아주 높이 jump 합니다.");
}
@Override
public void turn() {
System.out.println("한 바퀴 돕니다.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 고급자 레벨 입니다. *****");
}
}
public class Player {
private PlayerLevel level;
public Player()
{
level= new BeginnerLevel();
level.showLevelMessage();
}
public PlayerLevel getLevel() {
return level;
}
public void upgradeLevel(PlayerLevel level) {
this.level = level;
level.showLevelMessage();
}
public void play(int count){
level.go(count);
}
}
Player 객체는 생성될때, beginnerLevel 로 시작할 것이기 때문에 생성자에서 beginnerLevel로 생성해준다.
public class MainBoard {
public static void main(String[] args) {
Player player = new Player();
player.play(1);
AdvancedLevel aLevel = new AdvancedLevel();
player.upgradeLevel(aLevel);
player.play(2);
SuperLevel sLevel = new SuperLevel();
player.upgradeLevel(sLevel);
player.play(3);
}
}
다음과 같이 AdvancedLevel, SuperLevel 로 업그래드하면 다른 결과값이 나오는 것을 확인할 수 있다.
Observer Pattern
객체 사이에 일대다의 의존 관계가 있고, 어떤 객체의 상태변하게 되면 그 객체에 의존성을 가진 다른 객체들이 변화의 통지(notify or update)를 받고 자동으로 갱신될 필요가 있을때 사용하면 좋은 패턴이다.
하나의 객체에 연동되는 여러 객체 집합이 있을 때 변화에 대한 일관성은 유지하고, 객체간의 결합도는 낮게하기 위한 패턴이다.
변화에 관심이 있는 객체에 대한 가정없이 통보될 수 있어야 한다.
Class diagram
Subject: Observer를 알고 있는 주체, Observer를 더하거나 뺄 수 있다.
Observer: Subject의 변화에 관심을 가지는 객체, 갱신에 필요한 인터페이스 정의, 객체들의 일관성을 유지한다.
ConcreteSubject: ConcreteObserver에게 알려주어야하는 상태가 변경될때 통보한다. (주로 List로 Observer관리)
ConcreteObserver: 객체에 대한 참조자를 관리하고, Subject의 일관성 유지하며, Subject가 변경될 때 갱신되는 인터페이스를 구현한다.
예제 코드
public interface Observer {
public abstract void update(NumberGenerator generator);
}
class diagram 에서 Observer 에 해당된다.
public abstract class NumberGenerator {
private List<Observer> observers = new ArrayList<Observer>(); // Observer들을 보관
public void addObserver(Observer observer) { // Observer를 추가
observers.add(observer);
}
public void deleteObserver(Observer observer) { // Observer를 삭제
observers.remove(observer);
}
public void notifyObservers() { // Observer에 통지
Iterator<Observer> it = observers.iterator();
while (it.hasNext()) {
Observer o = it.next();
o.update(this);
}
}
public abstract int getNumber(); // 수를 취득한다.
public abstract void execute(); // 수를 생성한다.
}
class diagram 에서 Subject에 해당된다.
위와 같이, Subject와 Observer간의 추상적인 결합만이 존재한다.
위와 같이 Observer 를 리스트 형태로 가지고 있는 것을 확인할 수 있다.
notifyObservers 메서드에서 Observer 를 순회하면서 update 를 친다.
public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random(); // 난수발생기
private int number; // 현재의 수
public int getNumber() { // 수를 취득한다.
return number;
}
public void execute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
class diagram 에서 ConcreteSubject에 해당된다.
execute 메서드를 호출해서 notifyObservers 를 호출하고 notifyObservers 메서드에서 Observer 를 순회하며 update 를 친다.
public class DigitObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
class diagram 에서 ConcreteObserver에 해당된다.
NumberGenerator(Subject) 를 매개변수로 가지고 있는 것을 확인할 수 있다.
public class GraphObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.print("GraphObserver:");
int count = generator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
class diagram 에서 ConcreteObserver에 해당된다.
public class Main {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
Chain of Responsibility Pattern
어떠한 객체의 책임만으로 해결하기 힘든 문제를 책임을 떠넘겨서 해결할 수 있을 경우 사용하면 좋은 패턴이다.
다수의 객체를 사슬처럼 연결한다.
요청을 처리할 수 있는 기회를 하나 이상의 객체에게 부여한다.
요청을 해결할 객체를 만날 때까지 객체 고리를 따라서 요청을 전달한다.
메세지를 보내는 객체와 이를 받아서 처리하는 객체들 간의 결합도를 줄일 수 있다.
연결순서는 상황에 따라 바뀌거나 추가 삭제될 수 있다. 즉 객체의 책임을 추가, 변경, 확장할 수 있다.
하지만, 메세지가 항상 수신된다는것을 보장할 수 없다.
Class diagram
Handler
요청을 처리하는 인터페이스를 정의하고, 다음 번 처리자와의 연결을 구현한다.
연결고리에 연결된 다음 객체에게 다시 메세지를 보낸다.
ConcreteHandler
책임져야 할 메세지를 처리한다.
처리못하는 메세지는 다음 수신자에게 전달한다.
Client
ConcreteHandler 객체에게 필요한 요청을 보낸다.
예제 코드
public abstract class Support {
private String name; // 트러블 해결자의 이름
private Support next; // 떠넘기는 곳
public Support(String name) { // 트러블 해결자의 생성
this.name = name;
}
public Support setNext(Support next) { // 떠넘길 곳을 설정
this.next = next;
return next;
}
public final void support(Trouble trouble) { // 트러블 해결 순서
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
public String toString() { // 문자열 표현
return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble); // 해결용 메소드
protected void done(Trouble trouble) { // 해결
System.out.println(trouble + " is resolved by " + this + ".");
}
protected void fail(Trouble trouble) { // 미해결
System.out.println(trouble + " cannot be resolved.");
}
}
class diagram 에서 Handler 에 해당된다.
Chain 을 구현하기 위해서 setNext 의 반환값을 Support 자기자신으로 설정한다.
support 는 오버라이드 할 수 없도록 final 로 구현했다. (Template Method Pattern)
support 메서드는 문제를 해결하면 종료하고 해결하지 못하면 next 의 support 를 호출한다.
next 가 없을 때까지 진행했음에도 해결하지 못하면 실패한다.
public class LimitSupport extends Support {
private int limit; // 이 번호 미만이면 해결 할수 있다.
public LimitSupport(String name, int limit) { // 생성자
super(name);
this.limit = limit;
}
protected boolean resolve(Trouble trouble) { // 해결용 메소드
if (trouble.getNumber() < limit) {
return true;
} else {
return false;
}
}
}
class diagram 에서 ConcreteHandler에 해당된다.
public class SpecialSupport extends Support {
private int number; // 이 번호만 해결할 수 있다.
public SpecialSupport(String name, int number) { // 생성자
super(name);
this.number = number;
}
protected boolean resolve(Trouble trouble) { // 해결용 메소드
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}
public class OddSupport extends Support {
public OddSupport(String name) { // 생성자
super(name);
}
protected boolean resolve(Trouble trouble) { // 해결용 메소드
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
protected boolean resolve(Trouble trouble) { // 해결용 메소드
return false; // 자신은 아무 처리도 하지 않는다.
}
}
public class Main {
public static void main(String[] args) {
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSupport("Elmo");
Support fred = new LimitSupport("Fred", 300);
// 연쇄의 형성
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
// 다양한 트러블 발생
for (int i = 0; i < 500; i += 33) {
alice.support(new Trouble(i));
}
}
}
chain 을 형성해서 여러 객체가 하나의 일을 처리하도록 구현한다.
초기값은 NoSupport 인 alice 로 시작한다.
Interpreter Pattern
Class diagram
예제 코드
public abstract class Node {
public abstract void parse(Context context) throws ParseException;
}
public class ProgramNode extends Node {
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("program");
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString() {
return "[program " + commandListNode + "]";
}
}
public class CommandListNode extends Node {
private Vector list = new Vector();
public void parse(Context context) throws ParseException {
while (true) {
if (context.currentToken() == null) {
throw new ParseException("Missing 'end'");
} else if (context.currentToken().equals("end")) {
context.skipToken("end");
break;
} else {
Node commandNode = new CommandNode();
commandNode.parse(context);
list.add(commandNode);
}
}
}
public String toString() {
return "" + list;
}
}
public class CommandNode extends Node {
private Node node;
public void parse(Context context) throws ParseException {
if (context.currentToken().equals("repeat")) {
node = new RepeatCommandNode();
node.parse(context);
} else {
node = new PrimitiveCommandNode();
node.parse(context);
}
}
public String toString() {
return node.toString();
}
}
public class RepeatCommandNode extends Node {
private int number;
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("repeat");
number = context.currentNumber();
context.nextToken();
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString() {
return "[repeat " + number + " " + commandListNode + "]";
}
}
public class PrimitiveCommandNode extends Node {
private String name;
public void parse(Context context) throws ParseException {
name = context.currentToken();
context.skipToken(name);
if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
throw new ParseException(name + " is undefined");
}
}
public String toString() {
return name;
}
}
public class Context {
private StringTokenizer tokenizer;
private String currentToken;
public Context(String text) {
tokenizer = new StringTokenizer(text);
nextToken();
}
public String nextToken() {
if (tokenizer.hasMoreTokens()) {
currentToken = tokenizer.nextToken();
} else {
currentToken = null;
}
return currentToken;
}
public String currentToken() {
return currentToken;
}
public void skipToken(String token) throws ParseException {
if (!token.equals(currentToken)) {
throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");
}
nextToken();
}
public int currentNumber() throws ParseException {
int number = 0;
try {
number = Integer.parseInt(currentToken);
} catch (NumberFormatException e) {
throw new ParseException("Warning: " + e);
}
return number;
}
}
public class ParseException extends Exception {
public ParseException(String msg) {
super(msg);
}
}
public class Main {
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader("program.txt"));
String text;
while ((text = reader.readLine()) != null) {
System.out.println("text = \"" + text + "\"");
Node node = new ProgramNode();
node.parse(new Context(text));
System.out.println("node = " + node);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Iterator Pattern
다양한 순회방법이 제공하고 싶을 때 사용하면 좋은 패턴이다.
객체 요소들의 내부 표현방식을 공개하지 않는다.
내부에서 객체의 순차적인 제공을 하지 않는다.
외부에서, 여러 리스트 객체에 대한 동일한 방식으로 순회하는 방법을 제공하기 위해 순회하는 객체를 따로만듬
순회 구현 방식이 다르더라도 동일한 방식(메서드)로 순회 할 수 있게 제공한다.
class diagram
Iterator
요소에 접근하고 순회하는데 필요한 메서드 제공한다.
CocreteIterator
Iterator에 정의된 인터페이스를 구현하는 클래스다.
ConcreteIterator는 리스트를 순회하면서 각 리스트의 요소를 반환하는 메서드도 제공한다.
Aggregate
Iterator 객체를 생성하는 인터페이스 정의한다.
동일한 Aggregate를 구현한 클래스들은 동일한 방식으로 순회할 수 있다.
ConcreteAggregate
해당하는 ConcreteIteratir의 인스턴스를 반환하도록 Iterator 생성 인터페이스를 구현한다.
예제 코드
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
public interface Aggregate {
public abstract Iterator iterator(int type);
public int getLength();
}
public class Book {
private String name = "";
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
Factory f = IteratorFactory.getInstance();
public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
public Iterator iterator(int type) {
Iterator i = f.create(this, type);
return i;
}
}
IteratorFactory 이 싱글톤으로 구현되어 있기 때문에 static method 로 인스턴스를 가져온다.
iterator 메소드에서 IteratorFactory 의 상위 클래스인 Factory의 create 를 호출한다.
public class Constant {
public static final int FORWARD = 0;
public static final int REVERSE = 1;
}
public abstract class Factory {
public final Iterator create(Aggregate list, int type) {
Iterator p = createProduct(list, type);
return p;
}
protected abstract Iterator createProduct(Aggregate list, int type);
}
Template method 패턴으로 Factory 를 구현한다.
public class IteratorFactory extends Factory {
private static IteratorFactory ifactory = new IteratorFactory();
private IteratorFactory(){}
public static IteratorFactory getInstance(){
if(ifactory == null)
ifactory = new IteratorFactory();
return ifactory;
}
@Override
protected Iterator createProduct(Aggregate bookShelf, int type) {
if(type == Constant.FORWARD)
return new BookShelfIterator(bookShelf);
else if(type == Constant.REVERSE)
return new ReverseIterator(bookShelf);
else
return null;
}
}
다음과 같이, IteratorFactory 를 싱글톤으로 구성했다.
public class BookShelfIterator implements Iterator{
private BookShelf bookShelf;
private int index;
BookShelfIterator(Aggregate bookShelf) {
this.bookShelf = (BookShelf)bookShelf;
this.index = 0;
}
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
} else {
return false;
}
}
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
생성자로 Aggregate 를 받는데, 현재는 bookShelf 를 받는다.
public class ReverseIterator implements Iterator {
private BookShelf bookShelf;
private int index;
ReverseIterator(Aggregate bookShelf) {
this.bookShelf = (BookShelf)bookShelf;
this.index = bookShelf.getLength() -1;
}
public boolean hasNext() {
if (index >= 0 ) {
return true;
} else {
return false;
}
}
@Override
public Object next() {
Book book = bookShelf.getBookAt(index);
index--;
return book;
}
}
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
Iterator it = bookShelf.iterator(Constant.FORWARD);
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println("" + book.getName());
}
System.out.println("============");
it = bookShelf.iterator(Constant.REVERSE);
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println("" + book.getName());
}
}
}
위의 결과는 FORWARD 일때와 REVERSE 일때가 반대로 동작하는 것을 확인할 수 있다.
Visitor Pattern
SOILD 원칙에서는 클래스가 해야될 일은 해당 클래스 내에서 해야된다고 나온다.
하지만 Visitor Pattern 은 클래스 밖에서 일을 처리한다.
element에서 처리할 일을 visitor 가 처리한다.
여러 element에 대해 유사한 기능의 메서드를 한곳에 모아서 관리하게 되고, 여러 클래스에 걸친 메서드를 추가하기 용이하다.
각 클래스에 대한 기능이 자주 변경되거나 알고리즘의 변화가 많을때 사용하는것이 효율적이다.
각 객체의 오퍼레이션이 외부에 노출되는 것이므로 객체지향 프로그래밍의 캡슐화 전략에 위배된다.
주로 Composite 패턴에서 자주 사용될 수 있다.
class diagram
Visitor
각 객체에서 수행해야 할 기능을 선언한다.
메서드의 이름은 요청을 받을 객체를 명시한다.
ConcreteVisitor
Visitor 클래스에 선언된 메서드를 구현한다.
각 메서드는 객체에 해당하는 알고리즘을 구현한다.
Element
Visitor가 수행될 수 있는 Accept() 메서드를 선언한다.
ConcreteElement
매개변수로 Visitor를 받아주는 Accept()메서드를 구현한다.
ConcreteAggregate
해당하는 ConcreteIteratir의 인스턴스를 반환하도록 Iterator 생성 인터페이스를 구현한다.
ConcreteElement 들이 하는 일을 ConcreteVisitor 가 방문하면서 대신 처리한다.
Visitor 가 추가될 때 사용하면 좋은 패턴이나, Element 가 추가되는 경우에는 사용하면 좋지 않은 패턴이다.
새로운 요소 클래스를 추가하게 되면 그에 해당되는 기능을 모든 Visitor에서 구현해야 하기 때문이다.
예제 코드
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
public class ListVisitor extends Visitor {
private String currentdir = ""; // 현재 주목하고 있는 디렉토리명
public void visit(File file) { // 파일을 방문했을 때 호출된다.
System.out.println(currentdir + "/" + file);
}
public void visit(Directory directory) { // 디렉토리를 방문했을 때 호출된다.
System.out.println(currentdir + "/" + directory);
String savedir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator<Entry> it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
// if(entry.getName() == "tmp")
// continue;
entry.accept(this);
}
currentdir = savedir;
}
}
class diagram 에서 ConcreteVisitor에 해당된다.
위와같은 Visitor 들이 늘어나는 경우에 사용하면 좋은 패턴이다.
public interface Acceptor {
public abstract void accept(Visitor v);
}
public abstract class Entry implements Acceptor {
public abstract String getName(); // 이름을 얻는다.
public abstract int getSize(); // 사이즈를 얻는다.
public Entry add(Entry entry) throws FileTreatmentException { // 엔트리를 추가
throw new FileTreatmentException();
}
public Iterator iterator() throws FileTreatmentException { // Iterator의 생성
throw new FileTreatmentException();
}
public String toString() { // 문자열 표현
return getName() + " (" + getSize() + ")";
}
}
Acceptor 의 accept 메소드를 구현하고 있지 않기 때문에 abstract 클래스로 구현한다.
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(Visitor v) {
v.visit(this);
}
}
class diagram 에서 ConcreteElement 에 해당된다.
accept 메서드에서 visit 메서드를 호출하면서 인자로 this 를 넘긴다.
ConcreteElement 가 추가되는 경우 사용하면 좋지 않은 패턴이다.
public class Directory extends Entry {
private String name; // 디렉토리의 이름
private ArrayList<Entry> dir = new ArrayList<Entry>(); // 디렉토리 엔트리의 집합
public Directory(String name) { // 생성자
this.name = name;
}
public String getName() { // 이름을 얻는다.
return name;
}
public int getSize() { // 사이즈를 얻는다.
int size = 0;
Iterator<Entry> it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) { // 엔트리의 추가
dir.add(entry);
return this;
}
public Iterator<Entry> iterator() { // Iterator의 생성
return dir.iterator();
}
public void accept(Visitor v) { // 방문자를 받아들임
v.visit(this);
}
}
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.accept(new ListVisitor());
System.out.println("");
System.out.println("Making user entries...");
Directory Kim = new Directory("Kim");
Directory Lee = new Directory("Lee");
Directory Kang = new Directory("Kang");
usrdir.add(Kim);
usrdir.add(Lee);
usrdir.add(Kang);
Kim.add(new File("diary.html", 100));
Kim.add(new File("Composite.java", 200));
Lee.add(new File("memo.tex", 300));
Kang.add(new File("game.doc", 400));
Kang.add(new File("junk.mail", 500));
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
다음과 같이, new ListVisitor() 를 인자로 accept 를 호출하게 되면 ListVisitor 가 요소를 방문하면서 일을 처리한다.
ListVisitor 의 visit 을 호출할때 요소(this) 를 넘기기 때문이다.
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
Command Pattern
동일한 코맨드로 만들면 모을수 있어서 히스토리 관리를 할 수 있다. 객체 Array에 담을수 있기때문, 롤백도 할 수 있다.
코맨드를 한꺼번에 쭉 실행할 수도 있다.
Class diagram
Command: 각 명령이 수행할 메서드 선언
CocreteCommand: 실제 명령이 호출되도록 execute 구현
Client: ConcreteCommand 객체를 생성하고 처리 객체로 정의
Invoker: Command 처리를 수행할 것을 요청
Receiver: Command를 처리함
예제 코드
public interface Command {
public abstract void excute();
}
public class DrawCommand implements Command {
protected Drawable drawable;
private Point position;
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
public void execute() {
drawable.draw(position.x, position.y);
}
}
public class MacroCommand implements Command {
private Stack commands = new Stack();
public void execute() {
Iterator it = commands.iterator();
while (it.hasNext()) {
((Command) it.next()).execute();
}
}
public void append(Command cmd) {
if (cmd != this) {
commands.push(cmd);
}
}
public void undo() {
if (!commands.empty()) {
commands.pop();
}
}
public void clear() {
commands.clear();
}
}
위와 같이 command 를 쌓아 두었다가 처리 할 수 있다.
public interface Drawable {
public abstract void draw(int x, int y);
}
public class DrawCanvas extends Canvas implements Drawable {
private Color color = Color.red;
private int radius = 6;
private MacroCommand history;
public DrawCanvas(int width, int height, MacroCommand history) {
setSize(width, height);
setBackground(Color.white);
this.history = history;
}
public void paint(Graphics g) {
history.execute();
}
public void draw(int x, int y) {
Graphics g = getGraphics();
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
public class Main extends JFrame implements ActionListener,
MouseMotionListener, WindowListener {
private MacroCommand history = new MacroCommand();
private DrawCanvas canvas = new DrawCanvas(400, 400, history);
private JButton clearButton = new JButton("clear");
public Main(String title) {
super(title);
// 리스너 등록하기
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
history.clear();
canvas.repaint();
}
}
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
public void windowClosing(WindowEvent e) {
System.exit(0);
}
public void windowActivated(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowOpened(WindowEvent e) {
}
public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}
State Pattern
상태에 따른 기능을 분리하여 구현하는 패턴이다.
객체의 기능은 상태에 따라 달라질 수 있는데, 이러한 상태가 여러가지이고, 클래스 전반의 모든 기능이 상태에 의존적이라 하면, 상태를 클래스로 표현하는 것이 적절할때 사용하는 패턴이다.
클래스로 분리하지 않게 되면 상태가 여러가지인 경우 많은 if-else 문이 사용되고 추후 상태가 추가되거나 삭제될 때 수정해야 하는 사항이 너무 많아진다.
Class diagram
Context: ConcreteState의 인스턴스를 관리하고 서로 상태가 바뀌는 순간을 구현할 수 있다.
State: Context 가 사용할 메서드를 선언한다.
ConcreateState: 각 상태 클래스가 수행할 State에 선언된 메서드를 구현한다.
예제 코드
public abstract class PlayerLevel {
public abstract void run();
public abstract void jump();
public abstract void turn();
public abstract void showLevelMessage();
final public void go(int count)
{
run();
for(int i=0; i<count; i++){
jump();
}
turn();
}
}
class diagram 에서 State에 해당한다.
public class BeginnerLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("천천히 달립니다.");
}
@Override
public void jump() {
System.out.println("Jump 할 줄 모르지롱.");
}
@Override
public void turn() {
System.out.println("Turn 할 줄 모르지롱.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 초보자 레벨 입니다. *****");
}
}
class diagram 에서 ConcreteState에 해당한다.
public class AdvancedLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("높이 jump 합니다.");
}
@Override
public void turn() {
System.out.println("Turn 할 줄 모르지롱.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 중급자 레벨 입니다. *****");
}
}
class diagram 에서 ConcreteState에 해당한다.
public class SuperLevel extends PlayerLevel{
@Override
public void run() {
System.out.println("엄청 빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("아주 높이 jump 합니다.");
}
@Override
public void turn() {
System.out.println("한 바퀴 돕니다.");
}
@Override
public void showLevelMessage() {
System.out.println("***** 고급자 레벨 입니다. *****");
}
}
class diagram 에서 ConcreteState에 해당한다.
public class Player {
private PlayerLevel level;
public Player()
{
level= new BeginnerLevel();
level.showLevelMessage();
}
public PlayerLevel getLevel() {
return level;
}
public void upgradeLevel(PlayerLevel level) {
this.level = level;
level.showLevelMessage();
}
public void play(int count){
level.go(count);
}
}
class diagram 에서 Context 에 해당한다.
public class MainBoard {
public static void main(String[] args) {
Player player = new Player();
player.play(1);
AdvancedLevel aLevel = new AdvancedLevel();
player.upgradeLevel(aLevel);
player.play(2);
SuperLevel sLevel = new SuperLevel();
player.upgradeLevel(sLevel);
player.play(3);
}
}
위의 코드와 같이 다른 레벨을 인자로 넣어주면, 그 레벨에 해당되는 메서드가 실행된다.
State 가 추가되면, 새로운 클래스를 구현해 주면된다.
Memento Pattern
히스토리를 보관할때 사용되는 패턴이다.
만약 어느 시점으로 롤백을 해야되는 상황이 생긴다면 고려해볼만 패턴이다.
Class diagram
Memento
Originator 객체의 내부 상태를 필요한 만큼 저장한다.
Originator만이 Memento에 접근할 수 있다.
저장하고 싶은 변수 state 를 가지고 있다.
Originator
Memento를 생성하여 현재 객체의 상태를 저장하고 내부 상태를 복구한다.
저장하고 싶은 변수는 state이다.
CareTaker (undo mechanism) : Memento의 보관을 책임지기는 하지만, memento의 내부를 확인할 수 없다.
예제 코드
public class Main {
public static void main(String[] args) {
Gamer gamer = new Gamer(100); // 처음의 돈은 100
Memento memento = gamer.createMemento(); // 처음의 상태를 보존해 둔다.
ArrayList<Memento> history = new ArrayList<Memento>();
for (int i = 0; i < 100; i++) {
System.out.println("==== " + i); // 횟수 표시
System.out.println("현 상태:" + gamer); // 현재의 주인공의 상태 표시
gamer.bet(); // 게임을 진행 시킨다.
System.out.println("돈은" + gamer.getMoney() + "원이 되었습니다.");
// Memento의 취급 결정
if (gamer.getMoney() > memento.getMoney()) {
System.out.println(" (많이 증가했으니 현재의 상태를 보존해두자)");
memento = gamer.createMemento();
history.add(memento);
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println(" (많이 줄었으니 이전의 상태로 복귀하자)");
gamer.restoreMemento(memento);
}
// 시간을 기다림
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
}
Gamer 의 돈이 memento 가 가진 돈보다 많다면 상태를 저장한다.
또한, 돈이 줄었다면 restoreMemento 를 통해서 저장된 상태를 가져온다.
Memento 객체를 관리하고 있는 history 가 Caretaker 가 된다.
public class Gamer {
private int money;
private ArrayList<String> fruits = new ArrayList<>();
private Random random = new Random();
private static String[] fruitsname = {
"사과", "포도", "바나나", "귤",
};
public Gamer(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void bet() {
int dice = random.nextInt(6) + 1;
if(dice == 1) {
money += 100;
} else if (dice == 2) {
money /= 2;
} else if (dice == 6) {
String f = getFruit();
fruits.add(f);
} else {
System.out.println("아무일도 일어나지 않았습니다");
}
}
public Memento createMemento() {
Memento memento = new Memento(money);
Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
String f = it.next();
if (f.startsWith("good~ ")) { // 과일은 맛있는 것만 보존
m.addFruit(f);
}
}
return m;
}
public void restoreMemento(Memento memento) { // undo를 실행한다.
this.money = memento.money;
this.fruits = memento.fruits;
}
public String toString() { // 문자열 표현
return "[money = " + money + ", fruits = " + fruits + "]";
}
private String getFruit() { // 과일을 1개 얻는다.
String prefix = "";
if (random.nextBoolean()) {
prefix = "good~ ";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
createMemento 메서드에서 좋은 과일만 복원을 시키도록 저장한다.
public class Memento {
int money; // 돈
ArrayList<String> fruits; // 과일
public int getMoney() { // 돈을 얻는다.(narrow interface)
return money;
}
Memento(int money) { // 생성자(wide interface)
this.money = money;
this.fruits = new ArrayList<String>();
}
void addFruit(String fruit) { // 과일을 추가한다.(wide interface)
fruits.add(fruit);
}
}
Memento 에는 Gamer(Originator) 의 정보 중에서 기억하고 싶은 money, fruits 변수만 가지고 있다.