행위 패턴
- 반복적으로 사용되는 객체들의 상호작용을 패턴화한 것으로, 쿨래스나 객체들이 상호작용하는 방법과 책임을 분산하는 방법을 제공한다.
- 행위 패턴은 행위 관련 패턴을 사용하여 독립적으로 일을 처리하고자 할때 사용
- 종료
- 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 변수만 가지고 있다.
References
- 박은종 님의 디자인 패턴
'객체지향' 카테고리의 다른 글
프록시 패턴과 데코레이터 패턴 (0) | 2022.08.06 |
---|---|
생성 패턴 (디자인 패턴) (0) | 2022.05.15 |
구조패턴 (디자인 패턴) (0) | 2022.05.15 |
클래스 다이어그램 (0) | 2022.04.23 |
리플렉션을 활용한 동적 프락시 구현 (0) | 2022.03.10 |