๐ ์ต์ ๋ฒ ํจํด์ด๋?
์ต์ ๋ฒ ํจํด(Observer Pattern)์ ๊ฐ์ฒด ๊ฐ์ ์ผ๋๋ค ์์กด์ฑ์ ์ ์ํ๋ ํ๋ ๋์์ธ ํจํด์ ๋๋ค. ํ ๊ฐ์ฒด์ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋, ๊ทธ ๊ฐ์ฒด์ ์์กดํ๋ ๋ชจ๋ ๊ฐ์ฒด๋ค์ด ์๋์ผ๋ก ์๋ฆผ์ ๋ฐ๊ณ ์ ๋ฐ์ดํธ๋๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ด ํจํด์ ๋ฐํ-๊ตฌ๋ (Publish-Subscribe) ํจํด์ด๋ผ๊ณ ๋ ๋ถ๋ฆฌ๋ฉฐ, ๋์จํ ๊ฒฐํฉ(Loose Coupling)์ ํตํด ๊ฐ์ฒด ๊ฐ์ ์ํธ์์ฉ์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
๐๏ธ ์ต์ ๋ฒ ํจํด์ ๊ตฌ์กฐ
์ต์ ๋ฒ ํจํด์ ๋ค์๊ณผ ๊ฐ์ ์ฃผ์ ๊ตฌ์ฑ์์๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค:
๐ฏ Subject (์ฃผ์ )
- ์ต์ ๋ฒ๋ค์ ๋ชฉ๋ก์ ๊ด๋ฆฌ
- ์ต์ ๋ฒ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํ๋ ๋ฉ์๋ ์ ๊ณต
- ์ํ ๋ณ๊ฒฝ ์ ๋ชจ๋ ์ต์ ๋ฒ์๊ฒ ์๋ฆผ
๐๏ธ Observer (๊ด์ฐฐ์)
- Subject์ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ๊ธฐ ์ํ ์ธํฐํ์ด์ค ์ ์
- ์ ๋ฐ์ดํธ ๋ฉ์๋๋ฅผ ํตํด ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์
๐ข ConcreteSubject (๊ตฌ์ฒด์ ์ฃผ์ )
- Subject ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ
- ์ค์ ์ํ๋ฅผ ์ ์ฅํ๊ณ ๋ณ๊ฒฝ์ฌํญ์ ์ต์ ๋ฒ๋ค์๊ฒ ํต์ง
๐ค ConcreteObserver (๊ตฌ์ฒด์ ๊ด์ฐฐ์)
- Observer ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ
- Subject์ ์ํ ๋ณ๊ฒฝ์ ๋ํ ๊ตฌ์ฒด์ ์ธ ๋ฐ์ ์ ์
๐ป Java ์์ : ์ปดํจํฐ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ์์คํ
import java.util.ArrayList;
import java.util.List;
// Observer ์ธํฐํ์ด์ค
interface PerformanceObserver {
void update(String component, double usage);
}
// Subject ์ธํฐํ์ด์ค
interface PerformanceSubject {
void addObserver(PerformanceObserver observer);
void removeObserver(PerformanceObserver observer);
void notifyObservers();
}
// ConcreteSubject - ์ปดํจํฐ ์ฑ๋ฅ ๋ชจ๋ํฐ
class ComputerPerformanceMonitor implements PerformanceSubject {
private List<PerformanceObserver> observers;
private String currentComponent;
private double currentUsage;
public ComputerPerformanceMonitor() {
this.observers = new ArrayList<>();
}
@Override
public void addObserver(PerformanceObserver observer) {
observers.add(observer);
System.out.println("๐ ์๋ก์ด ๋ชจ๋ํฐ๋ง ์์คํ
์ด ๋ฑ๋ก๋์์ต๋๋ค.");
}
@Override
public void removeObserver(PerformanceObserver observer) {
observers.remove(observer);
System.out.println("โ ๋ชจ๋ํฐ๋ง ์์คํ
์ด ํด์ ๋์์ต๋๋ค.");
}
@Override
public void notifyObservers() {
for (PerformanceObserver observer : observers) {
observer.update(currentComponent, currentUsage);
}
}
public void setPerformanceData(String component, double usage) {
this.currentComponent = component;
this.currentUsage = usage;
System.out.println("๐ " + component + " ์ฌ์ฉ๋ฅ : " + usage + "%");
notifyObservers();
}
}
// ConcreteObserver - CPU ์๋ฆผ ์์คํ
class CPUAlert implements PerformanceObserver {
private String name;
public CPUAlert(String name) {
this.name = name;
}
@Override
public void update(String component, double usage) {
if (component.equals("CPU") && usage > 80.0) {
System.out.println("๐จ [" + name + "] CPU ์ฌ์ฉ๋ฅ ์ํ! " + usage + "% - ์ฆ์ ํ์ธ ํ์");
}
}
}
// ConcreteObserver - ๋ฉ๋ชจ๋ฆฌ ์๋ฆผ ์์คํ
class MemoryAlert implements PerformanceObserver {
private String name;
public MemoryAlert(String name) {
this.name = name;
}
@Override
public void update(String component, double usage) {
if (component.equals("Memory") && usage > 90.0) {
System.out.println("โ ๏ธ [" + name + "] ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ๊ฒฝ๊ณ ! " + usage + "% - ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ ๊ถ์ฅ");
}
}
}
// ConcreteObserver - ๋ก๊ทธ ์์คํ
class PerformanceLogger implements PerformanceObserver {
@Override
public void update(String component, double usage) {
System.out.println("๐ [๋ก๊ทธ] " + component + " ์ฌ์ฉ๋ฅ ๊ธฐ๋ก: " + usage + "%");
}
}
// ์ฌ์ฉ ์์
public class ComputerMonitoringExample {
public static void main(String[] args) {
// Subject ์์ฑ
ComputerPerformanceMonitor monitor = new ComputerPerformanceMonitor();
// Observer๋ค ์์ฑ ๋ฐ ๋ฑ๋ก
CPUAlert cpuAlert = new CPUAlert("์๋ฒ์ค ๊ด๋ฆฌ์");
MemoryAlert memoryAlert = new MemoryAlert("์์คํ
๊ด๋ฆฌ์");
PerformanceLogger logger = new PerformanceLogger();
monitor.addObserver(cpuAlert);
monitor.addObserver(memoryAlert);
monitor.addObserver(logger);
System.out.println("\n=== ์ปดํจํฐ ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ์์ ===");
// ์ฑ๋ฅ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ
monitor.setPerformanceData("CPU", 45.2);
System.out.println();
monitor.setPerformanceData("Memory", 67.8);
System.out.println();
monitor.setPerformanceData("CPU", 85.7); // CPU ๊ฒฝ๊ณ ๋ฐ์
System.out.println();
monitor.setPerformanceData("Memory", 94.3); // ๋ฉ๋ชจ๋ฆฌ ๊ฒฝ๊ณ ๋ฐ์
}
}
๐ TypeScript ์์ : ๋ฐ๋ค ์ํ๊ณ ๋ชจ๋ํฐ๋ง ์์คํ
// Observer ์ธํฐํ์ด์ค
interface MarineObserver {
update(location: string, temperature: number, depth: number): void;
}
// Subject ์ธํฐํ์ด์ค
interface MarineSubject {
addObserver(observer: MarineObserver): void;
removeObserver(observer: MarineObserver): void;
notifyObservers(): void;
}
// ConcreteSubject - ํด์ ํ๊ฒฝ ๋ชจ๋ํฐ
class OceanEnvironmentMonitor implements MarineSubject {
private observers: MarineObserver[] = [];
private currentLocation: string = "";
private currentTemperature: number = 0;
private currentDepth: number = 0;
addObserver(observer: MarineObserver): void {
this.observers.push(observer);
console.log("๐ ์๋ก์ด ํด์ ๊ด์ธก์๊ฐ ๋ฑ๋ก๋์์ต๋๋ค.");
}
removeObserver(observer: MarineObserver): void {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
console.log("๐ ํด์ ๊ด์ธก์๊ฐ ํด์ ๋์์ต๋๋ค.");
}
}
notifyObservers(): void {
this.observers.forEach(observer => {
observer.update(this.currentLocation, this.currentTemperature, this.currentDepth);
});
}
setEnvironmentData(location: string, temperature: number, depth: number): void {
this.currentLocation = location;
this.currentTemperature = temperature;
this.currentDepth = depth;
console.log(`๐ [${location}] ์์จ: ${temperature}°C, ์์ฌ: ${depth}m`);
this.notifyObservers();
}
}
// ConcreteObserver - ํด์ ์๋ฌผ ๋ณดํธ ์ผํฐ
class MarineLifeProtectionCenter implements MarineObserver {
private centerName: string;
constructor(centerName: string) {
this.centerName = centerName;
}
update(location: string, temperature: number, depth: number): void {
// ์์จ์ด ๋๋ฌด ๋๊ฑฐ๋ ๋ฎ์ผ๋ฉด ๊ฒฝ๊ณ
if (temperature > 28 || temperature < 10) {
console.log(`๐จ [${this.centerName}] ${location} ์ง์ญ ์์จ ์ด์! ${temperature}°C - ํด์์๋ฌผ ๋ณดํธ ์กฐ์น ํ์`);
}
// ๊น์ ๋ฐ๋ค ์๋ฌผ ์์์ง ๋ชจ๋ํฐ๋ง
if (depth > 1000) {
console.log(`๐ [${this.centerName}] ${location} ์ฌํด ์๋ฌผ ์์์ง ๊ฐ์ง - ์์ฌ ${depth}m`);
}
}
}
// ConcreteObserver - ๊ธฐ์์ฒญ ํด์ ์๋ณดํ
class WeatherMarineForecast implements MarineObserver {
update(location: string, temperature: number, depth: number): void {
// ์์จ ๋ณํ์ ๋ฐ๋ฅธ ๋ ์จ ์๋ณด ์
๋ฐ์ดํธ
if (temperature > 25) {
console.log(`โ๏ธ [๊ธฐ์์ฒญ] ${location} ์ง์ญ ๊ณ ์์จ์ผ๋ก ์ธํ ํญํ ๊ฐ๋ฅ์ฑ ๋ชจ๋ํฐ๋ง ์ค`);
} else if (temperature < 15) {
console.log(`โ๏ธ [๊ธฐ์์ฒญ] ${location} ์ง์ญ ์ ์์จ - ํ๋ฅ ์ํฅ ๋ถ์ ์ค`);
}
}
}
// ConcreteObserver - ์ด์
์ ๋ณด ์ผํฐ
class FishingInformationCenter implements MarineObserver {
update(location: string, temperature: number, depth: number): void {
// ์ดํ๋ ์์ธก์ ์ํ ํ๊ฒฝ ๋ถ์
if (temperature >= 18 && temperature <= 24 && depth <= 200) {
console.log(`๐ฃ [์ด์
์ ๋ณด์ผํฐ] ${location} ์ง์ญ ์ต์ ์ด์
์กฐ๊ฑด - ์์จ ${temperature}°C, ์์ฌ ${depth}m`);
} else if (depth > 500) {
console.log(`๐ฆ [์ด์
์ ๋ณด์ผํฐ] ${location} ์ฌํด์ด ์์์ง - ํน์ ์ด์
์ฅ๋น ํ์`);
}
}
}
// ConcreteObserver - ํด์ ๋ฐ์ดํฐ ๋ก๊ฑฐ
class OceanDataLogger implements MarineObserver {
private logData: Array<{location: string, temperature: number, depth: number, timestamp: Date}> = [];
update(location: string, temperature: number, depth: number): void {
const logEntry = {
location,
temperature,
depth,
timestamp: new Date()
};
this.logData.push(logEntry);
console.log(`๐ [๋ฐ์ดํฐ๋ก๊ฑฐ] ํด์ ๋ฐ์ดํฐ ์ ์ฅ๋จ - ${location}: ${temperature}°C, ${depth}m`);
}
getRecentLogs(count: number = 5): void {
console.log(`\n๐ ์ต๊ทผ ${count}๊ฐ ํด์ ๋ฐ์ดํฐ:`);
this.logData.slice(-count).forEach((log, index) => {
console.log(`${index + 1}. ${log.location} - ${log.temperature}°C, ${log.depth}m (${log.timestamp.toLocaleTimeString()})`);
});
}
}
// ์ฌ์ฉ ์์
class MarineMonitoringExample {
static run(): void {
// Subject ์์ฑ
const oceanMonitor = new OceanEnvironmentMonitor();
// Observer๋ค ์์ฑ ๋ฐ ๋ฑ๋ก
const protectionCenter = new MarineLifeProtectionCenter("์ ์ฃผ ํด์๋ณดํธ์ผํฐ");
const weatherForecast = new WeatherMarineForecast();
const fishingCenter = new FishingInformationCenter();
const dataLogger = new OceanDataLogger();
oceanMonitor.addObserver(protectionCenter);
oceanMonitor.addObserver(weatherForecast);
oceanMonitor.addObserver(fishingCenter);
oceanMonitor.addObserver(dataLogger);
console.log("\n=== ๐ ํด์ ํ๊ฒฝ ๋ชจ๋ํฐ๋ง ์์ ===");
// ๋ค์ํ ํด์ ํ๊ฒฝ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ
oceanMonitor.setEnvironmentData("์ ์ฃผ ๊ทผํด", 22.5, 150);
console.log();
oceanMonitor.setEnvironmentData("๋ํด ์ฌํด", 8.2, 1200);
console.log();
oceanMonitor.setEnvironmentData("์ํด ์ฐ์", 29.1, 45);
console.log();
oceanMonitor.setEnvironmentData("๋จํด ์ธํด", 19.8, 800);
console.log();
// ๋ฐ์ดํฐ ๋ก๊ทธ ํ์ธ
dataLogger.getRecentLogs();
}
}
// ์คํ
MarineMonitoringExample.run();
โ ์ต์ ๋ฒ ํจํด์ ์ฅ์
๐ ๋์จํ ๊ฒฐํฉ (Loose Coupling)
Subject์ Observer๋ ์๋ก์ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ ์์กดํ์ง ์๊ณ , ์ธํฐํ์ด์ค๋ฅผ ํตํด์๋ง ์ํธ์์ฉํฉ๋๋ค.
๐ ํ์ฅ์ฑ
์๋ก์ด Observer๋ฅผ ์ฝ๊ฒ ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, ๊ธฐ์กด ์ฝ๋๋ฅผ ์์ ํ์ง ์๊ณ ๋ ์์คํ ์ ํ์ฅํ ์ ์์ต๋๋ค.
๐ฏ ๋์ ๊ด๊ณ ์ค์
๋ฐํ์์ Observer๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํ ์ ์์ด ์ ์ฐํ ์์คํ ๊ตฌ์ฑ์ด ๊ฐ๋ฅํฉ๋๋ค.
๐ข ์ผ๋๋ค ํต์
ํ๋์ Subject๊ฐ ์ฌ๋ฌ Observer์๊ฒ ๋์์ ์๋ฆผ์ ๋ณด๋ผ ์ ์์ต๋๋ค.
โ ์ต์ ๋ฒ ํจํด์ ๋จ์
๐ ์ฑ๋ฅ ์ค๋ฒํค๋
Observer๊ฐ ๋ง์ ๊ฒฝ์ฐ ๋ชจ๋ Observer์๊ฒ ์๋ฆผ์ ๋ณด๋ด๋ ๋ฐ ์๊ฐ์ด ์์๋ฉ๋๋ค.
๐ ๋๋ฒ๊น ์ ์ด๋ ค์
Observer ์ฒด์ธ์ด ๋ณต์กํด์ง๋ฉด ํ๋ก๊ทธ๋จ์ ํ๋ฆ์ ์ถ์ ํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
๐พ ๋ฉ๋ชจ๋ฆฌ ๋์ ์ํ
Observer๋ฅผ ์ ๋๋ก ์ ๊ฑฐํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๐ ์ํ ์ข ์์ฑ
Observer์ Subject ๊ฐ์ ์ํ ์ข ์์ฑ์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
๐ฏ ์ธ์ ์ฌ์ฉํด์ผ ํ ๊น?
์ต์ ๋ฒ ํจํด์ ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์ ์ฉํฉ๋๋ค:
- ๐ ๋ชจ๋ธ-๋ทฐ ์ํคํ ์ฒ: GUI ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ ํ๋ฉด ์ ๋ฐ์ดํธ
- ๐ ์ด๋ฒคํธ ์์คํ : ์ฌ์ฉ์ ์ก์ ์ด๋ ์์คํ ์ด๋ฒคํธ์ ๋ํ ๋ฐ์
- ๐ ์ค์๊ฐ ๋ชจ๋ํฐ๋ง: ์์คํ ์ํ๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ถ์
- ๐ ์ํ ๋๊ธฐํ: ์ฌ๋ฌ ์ปดํฌ๋ํธ ๊ฐ์ ์ํ ์ผ์น ํ์
- ๐ข ์๋ฆผ ์์คํ : ํน์ ์กฐ๊ฑด ๋ฐ์ ์ ์ฌ๋ฌ ๋์์๊ฒ ์๋ฆผ
๐ ์ค์ ํ์ฉ ์ฌ๋ก
1. ๐ป ์น ๊ฐ๋ฐ
- React์ ์ํ ๊ด๋ฆฌ: Redux, MobX ๋ฑ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- DOM ์ด๋ฒคํธ: addEventListener๋ฅผ ํตํ ์ด๋ฒคํธ ์ฒ๋ฆฌ
- WebSocket ํต์ : ์ค์๊ฐ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
2. ๐ฑ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ: ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ UI ์๋ ์ ๋ฐ์ดํธ
- ํธ์ ์๋ฆผ: ์๋ฒ ์ด๋ฒคํธ์ ๋ฐ๋ฅธ ํด๋ผ์ด์ธํธ ์๋ฆผ
- ์ผ์ ๋ฐ์ดํฐ: GPS, ๊ฐ์๋๊ณ ๋ฑ์ ์ผ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
3. ๐ข ์ํฐํ๋ผ์ด์ฆ ์์คํ
- ๋ก๊น ์์คํ : ๋ค์ํ ๋ก๊ทธ ๋์์ ๋์ ๊ธฐ๋ก
- ๋ชจ๋ํฐ๋ง: ์์คํ ๋ฉํธ๋ฆญ ๋ณํ ๊ฐ์ง ๋ฐ ์๋ฆผ
- ์ํฌํ๋ก์ฐ: ๋น์ฆ๋์ค ํ๋ก์ธ์ค ๋จ๊ณ๋ณ ์ฒ๋ฆฌ
๐ก ๊ตฌํ ํ
1. ๐ Thread Safety
๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์๋ Observer ๋ชฉ๋ก ์ ๊ทผ ์ ๋๊ธฐํ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
private final List<Observer> observers =
Collections.synchronizedList(new ArrayList<>());
2. ๐ฏ ํ์ ์์ ์ฑ
์ ๋ค๋ฆญ์ ํ์ฉํ์ฌ ํ์ ์์ ์ฑ์ ๋์ผ ์ ์์ต๋๋ค.
interface Observer<T> {
void update(T data);
}
3. ๐๏ธ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
WeakReference๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
private final List<WeakReference<Observer>> observers = new ArrayList<>();
๐จ ๋ง๋ฌด๋ฆฌ
์ต์ ๋ฒ ํจํด์ ๊ฐ์ฒด ๊ฐ์ ๋์จํ ๊ฒฐํฉ์ ํตํด ์ ์ฐํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ์์คํ ์ ๊ตฌ์ถํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ๋์์ธ ํจํด์ ๋๋ค. ํนํ ์ด๋ฒคํธ ๊ธฐ๋ฐ ํ๋ก๊ทธ๋๋ฐ๊ณผ ์ค์๊ฐ ์์คํ ์์ ๊ทธ ์ง๊ฐ๋ฅผ ๋ฐํํฉ๋๋ค.
ํจํด์ ์ ์ฉํ ๋๋ ์์คํ ์ ๋ณต์ก์ฑ๊ณผ ์ฑ๋ฅ์ ๊ท ํ ์๊ฒ ๊ณ ๋ คํ์ฌ, ์ ์ ํ ์ํฉ์์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์์ ์์ ๋ค์ ์ฐธ๊ณ ํ์ฌ ์ฌ๋ฌ๋ถ๋ง์ ์ฐฝ์์ ์ธ ํ์ฉ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด์๊ธฐ ๋ฐ๋๋๋ค!
๋๊ธ