4차산업혁명의 일꾼/Java&Spring웹개발과 서버 컴퓨터

(State, Observer, Memento:상태저장)상태를 클래스로 표현하여 관리하는 패턴

르무엘 2023. 3. 5. 21:34

 

박은종의 객체지향 설계를 위한 디자인패턴 with 자바

 

조건에 따른 여러가지 상태를 클래스로 표현하여 관리하는 패턴(State, Observer, Memento)

상태를 관리

 

State

 

  • 객체의 기능은 상태에 따라 달라질 수 있는데, 이러한 상태가 여러가지이고, 클래스 전반의 모든 기능이 상태에 의존적이라 하면, 상태를 클래스로 표현하는 것이 적절함
  • 클래스로 분리하지 않게 되면 상태가 여러가지인 경우 많은 if-else 문이 사용되고 추후 상태가 추가되거나 삭제될 때 수정해야 하는 사항이 너무 많아짐
  • Context : ConcreteState의 인스턴스를 관리하고 서로 상태가 바뀌는 순간을 구현할 수 있다.
  • State : Context 가 사용할 메서드를 선언한다.
  • ConcreateState : 각 상태 클래스가 수행할 State에 선언된 메서드를 구현한다.
    • 상태에 따른 기능을 분리하여 구현
    • 새로운 상태가 추가되면 새로운 클래스를 추가한다.
    • 각 상태의 switch를 명확하게 구현해 함

 

(template 패턴 참조)

 

Observer

  • 하나의 객체에 연동되는 여러 객체 집합이 있을 때 변화에 대한 일관성은 유지하고, 객체간의 결합도는 낮게하기 위한 패턴
  • 변화에 관심이 있는 객체에 대한 가정없이 통보될 수 있도록 해야 함
  • 주로 data - view 의 관계에서 사용됨
  • log와 그 handler들의 관계. (file, console, 등등)

 

package com.backend.bakckend.designpattern.state;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

interface Observer {
    abstract void update(NumberGenerator generator);
}

abstract class NumberGenerator{
    private List<Observer> observers = new ArrayList<Observer>();
    void addObserver(Observer observer) {
        observers.add(observer);
    }
    void deleteObserver(Observer observer) {
        observers.remove(observer);
    }
    void notifyObservers() {
        Iterator<Observer> it = observers.iterator();
        while (it.hasNext()) {
            Observer o = it.next();
            o.update(this);
        }
    }
    abstract int getNumber(); // 숫자를 가져옴
    abstract void execute();  // 숫자를 생성하고 Observer에 통지
}

class RandomNumberGenerator extends NumberGenerator{
    private Random random = new Random();

    private int number;

    @Override
    int getNumber() {
        return number;
    }

    @Override
    void execute() {
        for (int i = 0; i < 20; i++) {
            number = (int) (Math.random() * 50);
            notifyObservers();
        }
    }
}

class DigitObserver implements Observer{
    @Override
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class GraphObserver implements Observer{
    @Override
    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) {
            e.printStackTrace();
        }
    }
}

public class ObserverTest {

    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();
    }


}

 

Momento

 

  • 내부 상태를 객체화하여, 나중에 객체가 이 상태로 복구 가능하게 함
  • 인스턴스의 상태를 보존해 두었다가 보존해 둔 정보를 가지고 인스턴스를 원래 상태로 복원
  • 인스턴스를 복원하기 위해서는 내부 정보에 자유롭게 접근 가능해야 함
  • 캡슐화가 파괴가 일어나지 않도록 주의해야 함

 

 

  • 이전의 상태로 되돌리는 undo
  • 했던 작업을 다시 하는 redo
  • 기억해야 하는 순간을 저장하는 객체
  • 오류를 복구하거나 수행 결과를 취소하기 위한 작업에 사용

 

  • Memento : Originator 객체의 내부 상태를 필요한 만큼 저장한다. Originator만이 Memento에 접근할 수 있다.
  • Originator : Memento를 생성하여 현재 객체의 상태를 저장하고 내부 상태를 복구
  • CareTaker (undo mechanism) : Memento의 보관을 책임지기는 하지만, memento의 내부를 확인할 수 없음
package com.backend.bakckend.designpattern.state;


import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

class Gamer{
    private int money;
    private ArrayList<String> fruits = new ArrayList<String>();
    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;
            System.out.println("소지금이 증가했습니다.");
        } else if (dice == 2) {
            money /= 2;
            System.out.println("소지금이 절반이 되었습니다.");
        } else if (dice == 6) {
            String f = getFruit();
            System.out.println("과일(" + f + ")을 받았습니다.");
            fruits.add(f);
        } else {
            System.out.println("변한 것이 없습니다.");
        }
    }

    public Memento createMemento() {
        Memento m = new Memento(money);
        Iterator<String> it = fruits.iterator();
        while (it.hasNext()) {
            String f = (String) it.next();
            if (f.startsWith("맛있는")) {
                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(){
        String prefix = "";
        if (random.nextBoolean()) {
             prefix = "맛있는";
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
   }

}


 class Memento {
   int money;
   ArrayList<String> fruits;
   Memento(int money){
      this.money = money;
      this.fruits = new ArrayList<String>();
   }
   void addFruit(String fruit){
      fruits.add(fruit);
   }
   public int getMoney() {
      return money;
   }

}

public class MementoTest {
    public static void main(String[] args) {
        Gamer gamer = new Gamer(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() + "원이 되었습니다.");

            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("");
        }
    }
}

 

LIST