Hyunsuk's Blog

Hyunsuk's Blog

목표

  • 옵저버 패턴 이해 및 구현

1. 옵저버 패턴

정의

  • 옵저버 패턴은 한 객체의 상태가 변경되면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 내용이 변경되는 일대다 의존성을 정의합니다

사용 시점

  • 객체 간의 일대다 의존성을 정의하고 싶을 때
  • 한 객체의 상태가 변경될 때 다른 객체들이 자동으로 알림을 받고 싶을 때
  • 느슨한 결합을 유지하면서 객체 간 통신이 필요할 때

클래스 다이어그램

classDiagram
    class Subject {
        <<interface>>
        +registerObserver(observer: Observer)
        +removeObserver(observer: Observer)
        +notifyObservers()
    }
    
    class Observer {
        <<interface>>
        +update(temperature: float, humidity: float, pressure: float)
    }
    
    class DisplayElement {
        <<interface>>
        +display()
    }
    
    class WeatherData {
        -observers: List~Observer~
        -temperature: float
        -humidity: float
        -pressure: float
        +registerObserver(observer: Observer)
        +removeObserver(observer: Observer)
        +notifyObservers()
        +measurementsChanged()
        +setMeasurements(temperature: float, humidity: float, pressure: float)
    }
    
    class CurrentConditionsDisplay {
        -weatherData: Subject
        -temperature: float
        -humidity: float
        +CurrentConditionsDisplay(weatherData: Subject)
        +update(temperature: float, humidity: float, pressure: float)
        +display()
    }
    
    class StatisticsDisplay {
        -weatherData: Subject
        -maxTemp: float
        -minTemp: float
        -tempSum: float
        -numReadings: int
        +StatisticsDisplay(weatherData: Subject)
        +update(temperature: float, humidity: float, pressure: float)
        +display()
    }
    
    class ForecastDisplay {
        -weatherData: Subject
        -currentPressure: float
        -lastPressure: float
        +ForecastDisplay(weatherData: Subject)
        +update(temperature: float, humidity: float, pressure: float)
        +display()
    }
    
    %% 인터페이스 구현 관계
    Subject <|.. WeatherData : implements
    Observer <|.. CurrentConditionsDisplay : implements
    Observer <|.. StatisticsDisplay : implements
    Observer <|.. ForecastDisplay : implements
    DisplayElement <|.. CurrentConditionsDisplay : implements
    DisplayElement <|.. StatisticsDisplay : implements
    DisplayElement <|.. ForecastDisplay : implements
    
    %% 구성 관계 (Composition)
    WeatherData *-- Observer : has
    
    %% 의존 관계 (Dependency)
    CurrentConditionsDisplay --> Subject : observes
    StatisticsDisplay --> Subject : observes
    ForecastDisplay --> Subject : observes

예시 코드

기상 스테이션과 다양한 디스플레이를 구현한 예시를 통해 옵저버 패턴을 살펴보겠습니다.

1. 주제(Subject) 인터페이스

1
2
3
4
5
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}

2. 옵저버(Observer) 인터페이스

1
2
3
public interface Observer {
void update(float temperature, float humidity, float pressure);
}

3. 디스플레이 인터페이스

1
2
3
public interface DisplayElement {
void display();
}

4. 기상 데이터 클래스 (주제)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;

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

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}

public void measurementsChanged() {
notifyObservers();
}

public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}

5. 현재 조건 디스플레이 (옵저버)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float temperature;
private float humidity;

public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}

@Override
public void display() {
System.out.println("현재 조건: " + temperature + "F 도, " + humidity + "% 습도");
}
}

6. 통계 디스플레이 (옵저버)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class StatisticsDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float maxTemp = 0.0f;
private float minTemp = 200.0f;
private float tempSum = 0.0f;
private int numReadings = 0;

public StatisticsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

@Override
public void update(float temperature, float humidity, float pressure) {
tempSum += temperature;
numReadings++;

if (temperature > maxTemp) {
maxTemp = temperature;
}

if (temperature < minTemp) {
minTemp = temperature;
}

display();
}

@Override
public void display() {
System.out.println("평균/최고/최저 온도 = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
}
}

7. 예보 디스플레이 (옵저버)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ForecastDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float currentPressure = 29.92f;
private float lastPressure;

public ForecastDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}

@Override
public void display() {
System.out.print("예보: ");
if (currentPressure > lastPressure) {
System.out.println("날씨가 좋아지고 있습니다!");
} else if (currentPressure == lastPressure) {
System.out.println("날씨가 그대로입니다.");
} else if (currentPressure < lastPressure) {
System.out.println("비가 올 것 같습니다.");
}
}
}

8. 테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();

CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

System.out.println("=== 첫 번째 측정 ===");
weatherData.setMeasurements(80, 65, 30.4f);

System.out.println("\n=== 두 번째 측정 ===");
weatherData.setMeasurements(82, 70, 29.2f);

System.out.println("\n=== 세 번째 측정 ===");
weatherData.setMeasurements(78, 90, 29.2f);
}
}

실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== 첫 번째 측정 ===
현재 조건: 80.0F 도, 65.0% 습도
평균/최고/최저 온도 = 80.0/80.0/80.0
예보: 날씨가 좋아지고 있습니다!

=== 두 번째 측정 ===
현재 조건: 82.0F 도, 70.0% 습도
평균/최고/최저 온도 = 81.0/82.0/80.0
예보: 비가 올 것 같습니다.

=== 세 번째 측정 ===
현재 조건: 78.0F 도, 90.0% 습도
평균/최고/최저 온도 = 80.0/82.0/78.0
예보: 날씨가 그대로입니다.

옵저버 패턴 장점

  1. 느슨한 결합: 주제와 옵저버가 느슨하게 결합되어 있습니다.
  2. 확장성: 새로운 옵저버를 쉽게 추가할 수 있습니다.
  3. 일대다 관계: 하나의 주제가 여러 옵저버에게 알림을 보낼 수 있습니다.
  4. 자동 알림: 주제의 상태가 변경되면 모든 옵저버가 자동으로 알림을 받습니다.

정리

  • 옵저버 패턴은 객체 간의 일대다 의존성을 정의할 때 사용합니다.
  • 한 객체의 상태가 변경될 때 다른 객체들이 자동으로 알림을 받고 싶을 때 사용합니다.
  • 느슨한 결합을 유지하면서 객체 간 통신이 필요할 때 사용합니다.

ref

  • 헤드퍼스트 디자인 패턴

목표

  • 컴포지션 사용

1. 컴포지션

사용 시점

  • 객체 간의 관계를 “has-a” 관계로 표현하고 싶을 때
  • 런타임에 객체의 동작을 변경하고 싶을 때
  • 상속 대신 유연한 구조가 필요할 때

클래스 다이어그램

classDiagram
    class WeaponBehavior {
        <<interface>>
        +useWeapon()
    }
    
    class Character {
        #weaponBehavior: WeaponBehavior
        +setWeaponBehavior(wb: WeaponBehavior)
        +performWeapon()
        +display()*
    }
    
    class Knight {
        +display()
    }
    
    class Archer {
        +display()
    }
    
    class SwordBehavior {
        +useWeapon()
    }
    
    class BowBehavior {
        +useWeapon()
    }
    
    WeaponBehavior <|.. SwordBehavior : implements
    WeaponBehavior <|.. BowBehavior : implements
    Character <|-- Knight : extends
    Character <|-- Archer : extends
    Character *-- WeaponBehavior : has

예시 코드

게임 캐릭터와 무기 시스템을 구현한 예시를 통해 컴포지션을 살펴보겠습니다.

1. 무기 행동 인터페이스

1
2
3
public interface WeaponBehavior {
void useWeapon();
}

2. 구체적인 무기 행동 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 검 사용
public class SwordBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("검으로 베기 공격!");
}
}

// 활 사용
public class BowBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("활로 화살 발사!");
}
}

3. 캐릭터 추상 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Character {
protected WeaponBehavior weaponBehavior;

public void setWeaponBehavior(WeaponBehavior wb) {
this.weaponBehavior = wb;
}

public void performWeapon() {
if (weaponBehavior != null) {
weaponBehavior.useWeapon();
} else {
System.out.println("무기가 없습니다!");
}
}

public abstract void display();
}

4. 구체적인 캐릭터 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 기사 클래스
public class Knight extends Character {
public Knight() {
// 기본적으로 검을 장착
weaponBehavior = new SwordBehavior();
}

@Override
public void display() {
System.out.println("나는 기사입니다!");
}
}

// 궁수 클래스
public class Archer extends Character {
public Archer() {
// 기본적으로 활을 장착
weaponBehavior = new BowBehavior();
}

@Override
public void display() {
System.out.println("나는 궁수입니다!");
}
}

5. 테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Game {
public static void main(String[] args) {
// 기사 생성
Character knight = new Knight();
knight.display();
knight.performWeapon();

// 궁수 생성
Character archer = new Archer();
archer.display();
archer.performWeapon();

// 무기 교체
System.out.println("기사가 활로 무기를 교체합니다!");
knight.setWeaponBehavior(new BowBehavior());
knight.performWeapon();

System.out.println("궁수가 검으로 무기를 교체합니다!");
archer.setWeaponBehavior(new SwordBehavior());
archer.performWeapon();
}
}

실행 결과

1
2
3
4
5
6
7
8
9
10
나는 기사입니다!
검으로 베기 공격!
나는 궁수입니다!
활로 화살 발사!

기사가 활로 무기를 교체합니다!
활로 화살 발사!

궁수가 검으로 무기를 교체합니다!
검으로 베기 공격!

컴포지션 장점

  1. 유연성: 런타임에 무기를 교체할 수 있습니다.
  2. 확장성: 새로운 무기나 캐릭터를 쉽게 추가할 수 있습니다.
  3. 재사용성: 무기 행동을 다른 클래스에서도 재사용할 수 있습니다.
  4. 캡슐화: 무기 관련 동작이 WeaponBehavior 인터페이스에 캡슐화되어 있습니다.

정리

  • 컴포지션은 객체 간의 관계를 “has-a” 관계로 표현하고 싶을 때 사용합니다.
  • 상속 대신 유연한 구조가 필요할 때 사용합니다.
  • 런타임에 객체의 동작을 변경하고 싶을 때 사용합니다.
  • 상속 대신 유연한 구조가 필요할 때 사용합니다.

ref

  • 헤드퍼스트 디자인 패턴

목표

리팩토링 스터디

리팩터링:예시 - chapter 1

1. 리팩터링이란?

리팩터링은 코드의 외부 동작은 그대로 유지한 채, 코드 구조를 개선하는 작업입니다. 마치 집을 리모델링하는 것처럼, 기존 기능은 그대로 유지하면서 내부 구조를 개선하는 것입니다.

2. 리팩터링이 필요한 이유

  • 코드의 가독성 향상
  • 버그 수정 용이성 증가
  • 새로운 기능 추가가 쉬워짐
  • 코드 재사용성 향상
  • 유지보수 비용 감소

3. 주요 리팩터링 기법

3.1 변수 관련 리팩터링

  • 변수 이름 바꾸기

    • 명확한 이름으로 변경하는 것을 주저하지 않기
    • IDE의 리팩터링 도구 활용 권장
  • 임시 변수 처리

    • 임시 변수는 질의 함수로 변경
    • 값이 바뀌지 않는 변수는 인라인화
    • 사용하는 변수는 바로 앞단에 위치시키기

3.2 함수 관련 리팩터링

  • 반복문 쪼개기

    • 기능별로 for문 분리
    • 주의사항: 성능 영향 확인 필요
      • 컴파일러 캐싱 기법으로 인한 성능 영향
      • 실제 실행 환경에서 테스트 필요
  • 문장 슬라이드

    • 관련 코드를 가까이 배치
    • 임시 변수를 질의 함수로 변경하기 쉽게 만듦

3.3 구조 개선 리팩터링

  • 단계 쪼개기

    • 계산 단계와 표현 단계 분리
    • 각 단계별 책임 명확화
  • 생성자를 팩터리 함수로 변경

    • JavaScript의 생성자 제한 해결
    • 서브클래스 인스턴스 반환 가능
  • 조건부 로직을 다형성으로 변경

    • 서브클래스별 로직 분리
    • 슈퍼클래스는 추상 메서드만 정의
    • 서브클래스에서 구체적 구현

3.4 데이터 처리 리팩터링

  • 얕은 복사 활용
    • 가변 데이터를 불변 데이터로 사용
    • 부작용 방지

4. 리팩터링 진행 단계

  1. 중첩 함수 분리

    • 여러 개의 독립적인 함수로 분리
    • 상위 함수 변수 접근 가능
  2. 단계 쪼개기

    • 계산 코드와 출력 코드 분리
    • 각 단계의 책임 명확화
  3. 다형성 적용

    • 계산 로직을 다형성으로 표현
    • 확장성과 유지보수성 향상

5. 좋은 코드의 기준

  • 수정하기 쉬운 코드
  • 이해하기 쉬운 코드
  • 재사용하기 쉬운 코드
  • 테스트하기 쉬운 코드

6. 리팩터링 시 주의사항

  • 테스트 코드 작성 필수
  • 작은 단계로 나누어 진행
  • 각 단계마다 테스트 실행
  • 성능 영향 고려

레퍼런스

  • 리팩터링2판

목표

  • 전략 패턴 사용
  • 옵저버 패턴 사용

1.전략 패턴 (Strategy Pattern)

사용 시점

  • 유사한 기능을 하는 여러 클래스가 존재할 때
    • 예: 다양한 결제 수단 (신용카드, 카카오페이, 토스페이)
    • 예: 다양한 인증 방식 (JWT, OAuth, Basic Auth)
    • 예: 다양한 캐싱 전략 (Redis, Memcached, Local Cache)
    • 예: 다양한 로깅 방식 (File, Console, Cloud)

구현 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from abc import ABC, abstractmethod

# 결제 수단 인터페이스 (Strategy)
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass

# 결제 수단 구현 (Concrete Strategies)
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"신용카드로 {amount}원 결제 완료!")

class KakaoPayPayment(PaymentStrategy):
def pay(self, amount):
print(f"카카오페이로 {amount}원 결제 완료!")

# 결제 프로세서 (Context)
class PaymentProcessor:
def __init__(self, payment_strategy: PaymentStrategy):
self.payment_strategy = payment_strategy

def pay(self, amount):
self.payment_strategy.pay(amount)

# 사용 예시
payment = PaymentProcessor(KakaoPayPayment())
payment.pay(10000)

2.옵저버 패턴 (Observer Pattern)

사용 시점

  • 한 객체의 상태 변화를 다른 객체들에게 알려야 할 때
    • 예: 결제 상태 변경 알림
    • 예: 이벤트 처리 시스템 (버튼 클릭, 키보드 입력)
    • 예: 실시간 데이터 업데이트 (주식 가격, 날씨 정보)
    • 예: 사용자 인터페이스 업데이트 (데이터 변경 시 화면 갱신)

구현 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 옵저버(Observer) 인터페이스
class Notifier:
def send_notification(self, message):
pass

# 알림 방식 구현
class SMSNotifier(Notifier):
def send_notification(self, message):
print(f"📱 SMS: {message}")

class EmailNotifier(Notifier):
def send_notification(self, message):
print(f"📧 Email: {message}")

# 결제 프로세서 (Subject)
class PaymentProcessor:
def __init__(self):
self.notifiers = []

def add_notifier(self, notifier: Notifier):
self.notifiers.append(notifier)

def pay(self, amount):
print(f"💳 {amount}원 결제 완료!")
for notifier in self.notifiers:
notifier.send_notification(f"{amount}원 결제가 완료되었습니다.")

# 사용 예시
payment = PaymentProcessor()
payment.add_notifier(SMSNotifier())
payment.add_notifier(EmailNotifier())
payment.pay(20000)

정리

각 디자인 패턴은 특정 상황에서 코드의 유지보수성과 확장성을 높이는 데 도움을 줍니다. 실제 프로젝트에서는 이러한 패턴들을 적절히 조합하여 사용하는 것이 중요합니다.

목표

docker 카프카 RKaft 모드(no zookeepler) server 만들기

과정

  1. 윈도우 wsl 우분투 설치
  2. 카프카 docker pull 하기
  3. 카프카 실행시키기
  4. 메세지 보내보기

윈도우에 리눅스 설치

  1. 윈도우 기능 켜기 끄기에서 linux용 하위 시스템 활성화
  1. wsl -l -o 입력하여 설치 가능한 배포 확인
  2. wsl –install [배포판]으로 설치
  3. wsl 입력해서 가상 리눅스로 접속
더 읽어보기 »
0%