Java/고급객체지향

고급객체지향 프로그래밍 - 옵서버 패턴 (Observer Pattern)

얄루몬 2021. 10. 18. 22:37

옵저버 패턴 (Observer Pattern)

 

옵저버 패턴 (Observer Pattern)이란?

옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다.

📌출처: https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4

 

옵저버 패턴은 일종의 푸쉬 서비스를 구현한다.

예를 들어 설명하자면 뉴스레터와 구독자가 합쳐진 것이 옵저버 패턴이다. (발행자는 subject / 구독자는 observer)

 

요소 설명
이름 옵저버(Observer)
문제 1:n 관계에서의 정보 갱신
해결방안 사용자를 등록하고, 정보가 변동하는 경우 알려주고 값을 자동으로 갱신한다.
결과 느슨한 커플링(loose coupling), 확장형

 

느슨한 결합이란?

- 두 객체가 느슨하게 결합되어 있다는 것은, 그 둘이 상호작용을 하긴 하지만 서로에 대해서 잘 모른다는 것을 의미한다.

- 옵저버 패턴에서는 서브젝트와 옵저버간 느슨한 결합이 만들어진다. 

    - 서브젝트가 옵저버에 대해서 아는 것은 특정 인터페이스를 구현한다는 것뿐이다.

    - 새로운 옵저버는 쉽게 추가하거나 제거 가능(실행 중에도 가능) 

    - 옵저버가 새로 생겨도 subject는 바뀌지 않는다.

    - 서브젝트와 옵저버는 독립적으로 재사용이 가능하다.

 


package oop_3;

import javax.swing.*;
import java.awt.event.WindowListener;

public abstract class FrameWindow {
    private JFrame frame;

    public FrameWindow(String title, int x, int y, int width, int height) {
        frame = createWindow(title, x, y, width, height);
    }

    public FrameWindow(String title, int x, int y, int width, int height, WindowListener lis) {
        frame = createWindow(title, x, y, width, height);
        frame.addWindowListener(lis);
    }

    public JFrame createWindow(String title, int x, int y, int width, int height) {
        JFrame frame;
        frame = new JFrame(title);
        frame.setBounds(x, y, width, height);
        JPanel panel = createPanel(width, height);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setVisible(true);
        return frame;
    }

    public void closeWindow() {
        frame.setVisible(false);
        frame.dispose();
    }

    public void addWindowListener(WindowListener lis) {
        frame.addWindowListener(lis);
    }

    public abstract JPanel createPanel(int width, int height);
}
package oop_3;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class MainWindow extends FrameWindow implements ActionListener {
    private static final String MAIN_TITLE = "Main Window";
    private static final String TEXTFIELD_WINDOW_TITLE = "TextField Window";
    private static final String LABEL_WINDOW_TITLE = "Label Window";
    private static final String REMOVE_TEXTFIELD_OBSERVER_BUTTON_TITLE = "Remove TextField Window Observer";
    private static final String REMOVE_LABEL_OBSERVER_BUTTON_TITLE = "Remove Label Window Observer";
    private static final String ADD_TEXTFIELD_OBSERVER_BUTTON_TITLE = "Add TextField Window Observer";
    private static final String ADD_LABEL_OBSERVER_BUTTON_TITLE = "Add Label Window Observer";
    private static final String STOP_THREAD_BUTTON_TITLE = "Stop Generating Prime Number";
    private static final int X = 250;
    private static final int Y = 100;
    private static final int WIDTH = 600;
    private static final int HEIGHT = 200;
    private static final int GAP = 50;

    private boolean labelObserverAdded = true;
    private boolean textFieldObserverAdded = true;
    private JButton stopButton;
    private JButton updateTextFieldObserverButton;
    private JButton updateLabelObserverButton;
    private PrimeObservableThread primeThread;
    private TextFieldWindow textFieldWindow;
    private LabelWindow labelWindow;

    public MainWindow(String title) {
        super(title, X, Y, WIDTH, HEIGHT);
        textFieldWindow = new TextFieldWindow(TEXTFIELD_WINDOW_TITLE, X, Y + HEIGHT + GAP, WIDTH, HEIGHT);
        labelWindow = new LabelWindow(LABEL_WINDOW_TITLE, X, Y + (HEIGHT + GAP) * 2, WIDTH, HEIGHT);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                primeThread.stopRunning();
                textFieldWindow.closeWindow();
                labelWindow.closeWindow();
                System.exit(0);
            }
        });

        primeThread = new PrimeObservableThread(); // 객체 생성
        primeThread.addObserver(textFieldWindow);
        primeThread.addObserver(labelWindow);
        primeThread.run();  // 소수 생성 시작. 이 함수가 실행된 후에는 stopButton이 눌리기 전까지 무한 반복됨
    }

    public JPanel createPanel(int width, int height) {
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        panel.setPreferredSize(new Dimension(width, height));
        updateTextFieldObserverButton = createButton(REMOVE_TEXTFIELD_OBSERVER_BUTTON_TITLE, this, width, height);
        panel.add(updateTextFieldObserverButton);
        updateLabelObserverButton = createButton(REMOVE_LABEL_OBSERVER_BUTTON_TITLE, this, width, height);
        panel.add(updateLabelObserverButton);
        stopButton = createButton(STOP_THREAD_BUTTON_TITLE, this, width, height);
        panel.add(stopButton);
        return panel;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == updateTextFieldObserverButton) {
            if (textFieldObserverAdded) {
                primeThread.removeObserver(textFieldWindow);
                updateTextFieldObserverButton.setText(ADD_TEXTFIELD_OBSERVER_BUTTON_TITLE);
                textFieldObserverAdded = false;
            }
            else {
                primeThread.addObserver(textFieldWindow);
                updateTextFieldObserverButton.setText(REMOVE_TEXTFIELD_OBSERVER_BUTTON_TITLE);
                textFieldObserverAdded = true;
            }
        }
        else if (e.getSource() == updateLabelObserverButton) {
            if (labelObserverAdded) {
                primeThread.removeObserver(labelWindow);
                updateLabelObserverButton.setText(ADD_LABEL_OBSERVER_BUTTON_TITLE);
                labelObserverAdded = false;
            }
            else {
                primeThread.addObserver(labelWindow);
                updateLabelObserverButton.setText(REMOVE_LABEL_OBSERVER_BUTTON_TITLE);
                labelObserverAdded = true;
            }
        }
        else if (e.getSource() == stopButton) {
            primeThread.stopRunning();
        }
    }

    private JButton createButton(String text, ActionListener listener, int width, int height) {
        JButton button = new JButton(text);
        button.addActionListener(listener);
        Dimension buttonDimension = new Dimension(width, height / 3);
        button.setMaximumSize(buttonDimension);
        button.setMinimumSize(buttonDimension);
        button.setPreferredSize(buttonDimension);
        return button;
    }

    public static void main(String[] args) {
        MainWindow mainWindow = new MainWindow(MainWindow.MAIN_TITLE);
    }
}
package oop_3;

import java.util.ArrayList;

public class PrimeObservableThread implements Runnable, Observable {
    private static final int SLEEPTIME = 500;

    private int primeNumber;
    private int numCount;
    private boolean first = true;
    private boolean stopRunning = false;

    ArrayList<Observer> observers;

    public PrimeObservableThread() {
        observers = new ArrayList<Observer>();
    }

    public void addObserver(Observer o) {
//        if (observers.contains(o)) {
//            System.out.println("Observers already contained the observer.")
//        }
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    public void removeObserver(Observer o) {
        if (observers.contains(o)) {
            observers.remove(o);
        }
    }

    public void notifyObservers() {
        for (Observer o : observers) {
            o.updateText("" + primeNumber);
        }
    }

    public int getPrimeNumber() {
        return primeNumber;
    }

    public void stopRunning() {
        stopRunning = true;
    }

    public void startRunning() {
        stopRunning = false;
        run();
    }

    private void generatePrimeNumber() {
        while (stopRunning == false) {
            if (first) {
                first = false;
                primeNumber = 2;   // 泥� 踰덉㎏ �냼�닔�뒗 2
                System.out.println(primeNumber);
                numCount = 1; // �떎�쓬 �떒怨꾨��꽣�뒗 2瑜� �뜑�빐�꽌 ���닔留� �솗�씤�븯誘�濡� 1濡� 諛붽퓭�꽌 �떎�쓬 �닽�옄瑜� 3�쑝濡� 留뚮뱾�뼱�빞 �븿
            } else {
                numCount += 2; // 2瑜� �젣�쇅�븳 吏앹닔�뒗 �냼�닔媛� �맆 �닔 �뾾�쓬. �뵲�씪�꽌 ���닔留� 寃��궗
                if (isPrimeNumber(numCount)) {
                    primeNumber = numCount;
                    notifyObservers();
                    System.out.println(primeNumber);
                }
            }
            try {
                Thread.sleep(SLEEPTIME); // 1珥� �돹
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private boolean isPrimeNumber(int n) {
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void run() {
        generatePrimeNumber();
    }
}
package oop_3;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class TextFieldWindow extends FrameWindow implements Observer {
    private JTextField textField;

    public TextFieldWindow(String title, int x, int y, int width, int height) {
        super(title, x, y, width, height);
    }

    public void updateText(String msg) {
        textField.setText(msg);
        textField.validate();
    }

    public JPanel createPanel(int width, int height) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        textField = new JTextField();
        panel.add(textField);
        panel.setPreferredSize(new Dimension(width, height));
        return panel;
    }
}
package oop_3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class LabelWindow extends FrameWindow implements Observer {
    private JLabel label;

    public LabelWindow(String title, int x, int y, int width, int height) {
        super(title, x, y, width, height);
    }

    public void updateText(String msg) {
        label.setText(msg);
        label.validate();
    }

    public JPanel createPanel(int width, int height) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        label = new JLabel();
        panel.add(label);
        panel.setPreferredSize(new Dimension(width, height));
        return panel;
    }
}
package oop_3;
public interface Observer {
    void updateText(String s);
}
package oop_3;

public interface Observable {
    void addObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}