객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. (위키 백과)
잘 와닿지 않는다. 간단한 빙고 게임을 생각해보자. 심판(Referee)이 임의의 숫자를 하나씩 부르고, 플레이어(Player)들은 심판이 부른 숫자를 보드판에 마킹하여 빙고를 찾아내는 게임이다. 단순하게 구현하면 아래와 같은 코드를 생각해볼 수 있다.
while (!hasWinner()) {
int CalledNumber = Referee.getCalledNumber();
player.markBoard(calledNumber);
player.checkBingo();
for (var player : players) {
if (player.isBingo()) {
winners.add(player);
hasWinner = true;
}
}
if (hasWinner) {
System.out.printf("우승자는 ");
for (var winner : winners) {
System.out.printf("winner.getName()), ");
}
System.out.println(" 입니다.");
}
}
전체 코드: https://github.com/ji-jjang/Learning/commit/cee8f544da3e5d368cb260d012d4ac5dad96bbfd
public class BingoGame {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (var o : observers) {
o.update(this);
}
}
}
만약 관찰자들을 관리하는 주체가 위의 기본 기능을 가지면서, 상황에 따라 다른 기능을 포함해야 한다면 추상 클래스로 선언하고 해당 추상 클래스를 상속한 클래스에서 재정의하게 설계하면 좋다.
관찰자는 자신의 역할에 따라 행동을 다르게 할 수 있도록 인터페이스만 제공한다.
public interface Observer {
public abstract void update(NumberGenerator generator);
}
public class Player implements Observer {
private String name;
private int[][] board;
private boolean isBingo;
@Override
public void update(BingoGame game) {
markBoard(game.getBingoNumber());
checkBingo();
}
}
public class Referee implements Observer {
private List<Integer> calledNumbers = new ArrayList<>();
Random random = new Random();
@Override
public void update(BingoGame game) {
int number = 0;
do {
number = random.nextInt(game.getMaxNumber() + 1);
} while (calledNumbers.contains(number));
System.out.println("call: " + number);
game.setBingNumber(number);
calledNumbers.add(number);
}
}
public class WinnerChecker implements Observer {
private List<String> winners = new ArrayList<>();
@Override
public void update(BingoGame game) {
for (var observer : game.getObservers()) {
if (observer instanceof Player) {
if (((Player) observer).isBingo()) {
winners.add(((Player) observer).getName());
}
}
}
if (winners.size() > 0) {
game.setHasWinner(true);
System.out.printf("우승자는 {");
for (var e : winners) {
System.out.printf(e + " ");
}
System.out.println("} 입니다.");
}
}
}
public static void main(String[] args) throws IOException {
BingoGame bingoGame = new BingoGame();
for (int i = 0; i < bingoGame.getPlayerCount(); ++i) {
Player player = new Player("player " + i, bingoGame);
bingoGame.addObserver(player);
}
Referee referee = new Referee();
bingoGame.addObserver(referee);
WinnerChecker winnerChecker = new WinnerChecker();
bingoGame.addObserver(winnerChecker);
bingoGame.startGame();
}
public void startGame() {
while (!hasWinner) {
notifyObservers();
}
}
코드 링크: https://github.com/ji-jjang/ebrainsoft/tree/main/BingoGame
{gameStart && (
<>
<Referee
callNumber={callNumber}
isGameOver={isGameOver}
calledNumbers={calledNumbers}
/>
<div className="boards-container">
{!isNaN(players) &&
Array(players)
.fill()
.map((undefined, playerIndex) => (
<Player
key={playerIndex}
playerIndex={playerIndex}
rows={rows}
cols={cols}
maxNumber={maxNumber}
calledNumbers={calledNumbers}
handleWin={() => handleWinners(playerIndex)}
isGameOver={isGameOver}
/>
))}
</div>
<WinnerChecker
winningPlayers={winningPlayers}
players={players}
setIsGameOver={setIsGameOver}
/>
</>
)}