헤드 퍼스트 디자인 패턴 with TypeScript - Strategy Pattern, Observer Pattern

기운찬곰·2023년 2월 4일
0

디자인 패턴

목록 보기
1/1

시작하기에 앞서. 디자인 패턴을 배우는 이유

저는 예전에 디자인 패턴하면 패턴 종류만 많고 저걸 다 알아야 하나? 싶었습니다. 처음 디자인 패턴을 찾아본것도 면접에서 물어볼까봐 암기식으로 찾아본 것이 전부. 어차피 그거 몰라도 코드 구현하는데 아무 문제 없다고 느꼈고 저는 그저 아는 지식을 정리해둔 것에 불가하다 라고 생각했습니다. 즉, 용어만 많고 어차피 아는 내용… 이라고 봤었습니다.

근데 이제 와서 디자인 패턴을 좀 제대로 보려고 하는 이유는 최소한 디자인 패턴도 모르는 개발자가 되고 싶지 않아서 입니다. 지금은 1년이 좀 지난 경력을 가진 개발자이지만 나중에 후임이 들어와서 물어볼 일은 없겠지만 혹여 물어봤는데 제대로 대답 못하면 창피할 거 같아서… 그리고 생각보다 헤드 퍼스트 디자인 패턴 책이 재밌기도 했습니다. 필요한 패턴 하나하나마다 예시랑 발전되어가는 코드를 보고 있으면 오… 그렇구나 라며 저도 모르게 흥미를 느끼게 됩니다.

어쨌거나 지금은 디자인 패턴은 직업마다 전문 용어가 있듯이 개발자라면 이런 용어(패턴)쯤은 알아야 되지 않을까 라는 생각으로 바뀌게 되었습니다.


책에서 설명하는 디자인 패턴을 배워야 하는 이유

그렇다면 헤드 퍼스트 디자인 패턴이란 책에서는 디자인 패턴을 왜 배워야 한다고 설명하고 있을까요?

  • 상속, 다형성, 추상화 등 객체지향 개념을 알고 개발하면 되지 굳이 디자인 패턴을 배워야 하는 이유에 대해서는 좋은 객체지향을 가진 시스템을 구축하는건 그리 간단하지 않습니다. 그러니 디자인 패턴 같은 노하우를 익혀서 상황에 맞는 패턴을 쓰는 게 도움이 될 것이다. 라고 말하고 있습니다.
  • 확실히 디자인 패턴은 오래된 내용이고 수십년간 검증된 내용이기 때문에 지금도 인기있을 거라 생각합니다.

01. Strategy Pattern (전략 패턴)

전략 패턴을 한 문장으로 정의하면 "바뀔 수 있는 부분을 알고리즘 군을 정의하고 캡슐화해서 손쉽게 수정할 수 있도록 독립하는 방법" 이라고 설명할 수 있습니다.

책에서는 오리 시뮬레이션을 예로 들고 있습니다.

  • 오리라는 부모 클래스가 있습니다.
  • 그리고 오리를 상속받아서 구현하는 여러가지 오리 품종이 있습니다. 예를 들어, Mallardduck(청동오리), Rouenduck(르왕오리) 등.
  • 오리의 행동을 생각해봅시다. 먼저 날 수 있는 행위를 할 수 있겠죠? 엄청 빠른속도로 날 수도 있을 것이고, 느릴 수도 있습니다. 혹은 못 날 수도 있겠죠.
  • 오리의 행동 중에 꽥꽥 소리도 낼 수 있습니다. 혹은 꽉꽉 이란 소리도 낼 수 있고, 소리를 내지 않을 수도 있겠죠?

중요한 점은 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리해야 합니다. 날 수 있는 행위, 소리를 내는 행위 모두 변할 수 있는 부분입니다. 이런 부분을 알고리즘 군을 만들어서 관리하자는 것이 전략 패턴입니다.

날 수 있는 행위를 FlyBehavior 인터페이스를 만들고, 구체적인 행위를 FlyBehavior를 상속받아서 구현시켜보면 어떨까요? FlyWithWings(나는 행동), FlyNoWay(날 수 없는 행동) 등 으로 정의해봅시다.인터페이스는 객체 지향에서 참으로 중요합니다. 구체적인 구현보다는 인터페이스에 맞춰 프로그래밍을 해야 다형성을 제대로 사용할 수 있으니까요.

이렇게 FlyBehavior 인터페이스를 상속받아서 구체적인 행위를 구현한 알고리즘 군을 만들었습니다. 이제 Mallardduck(청동오리), Rouenduck(르왕오리) 등 오리 클래스는 이런 Fly라는 알고리즘 군을 원하는대로 가져다가 쓸 수 있고 변경도 가능합니다. 참 좋죠?

💻 타입스크립트 코드 구현 참고 : https://github.com/ckstn0777/Typescript-Design-Pattern/tree/main/StrategyPattern


02. Observer Pattern (옵저버 패턴)

옵저버 패턴을 한 문장으로 정의하면 “한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의하는 방법”으로 설명할 수 있습니다.

책에서는 온도, 습도, 기압센서 등을 통해 데이터를 수집해서 디스플레이마다 보여주는 예를 들고 있습니다.

아래는 옵저버 패턴 적용 전 예시입니다. 구체적인 구현에 맞춰서 코딩되어있기 때문에 변화가 생기면 매번 변경해야 합니다. 변화가 있을만한 부분은 캡슐화하는 것이 중요합니다.

public class WeaterData {
	
	// 데이터가 갱신될 때마다 자동 실행된다고 가정
	public void measurementsChanged() {
		// 현재 온도, 습도, 기압 정보를 불러온다. 
		float temp = getTemperature()
		float humidity = getHumidity()
		float pressure = getPressure()

		// 이를 각각의 디스플레이에 보여준다. (정말 단순하다)
		// 하지만 디스플레이가 추가 혹은 제거될 때마다 매번 변경해야 한다. (구체적인 구현이기 때문)
		currentConditionDisplay.update(temp, humidity, pressure);
		statisiticsDisplay.update(temp, humidity, pressure);
		...
	}
}

느슨한 결합(Loose Coupling)이란 객체들이 상화작용할 수는 있지만, 서로를 잘 모르는 관계를 의미합니다. 옵저버 패턴은 느슨한 결합을 보여주는 휼륭한 예입니다.

test('Test Observer Pattern - WeatherStation Display Simulator', () => {
  const weatherStation = new WeatherStation()

  // 디스플레이(옵저버, 구독자) 등록. 
  const currentConditionsDisplay = new CurrentConditionsDisplay(weatherStation)
  const statisticsDisplay = new StatisticsDisplay(weatherStation)

  // 데이터 업데이트 시 옵저버 update 를 실행시켜서 알려주게 된다. 
  weatherStation.setMeasurements(7, 55, 30.4)

  // 결과:
  // Current conditions: 온도 7 도, 습도 55 % 입니다.
})
import ISubject from './ISubject'
import IObserver from './IObserver'

// WeatherStation이 곧 Subject
export default class WeatherStation implements ISubject {
  private observers: IObserver[]
  private temperature: number
  private humidity: number
  private pressure: number

  constructor() {
    this.observers = [] // 옵저버 등록
  }

  registerObserver(o: IObserver) {
    this.observers.push(o)
  }

  removeObserver(o: IObserver) {
    console.log(o)
    let index = this.observers.indexOf(o)
    if (index !== -1) {
      this.observers.splice(index, 1)
    }
  }

  notifyObserver() {
    for (let i = 0; i < this.observers.length; i++) {
      this.observers[i].update(this.temperature, this.humidity, this.pressure)
    }
  }

  // 기상 스테이션으로부터 갱신된 측정값을 받으면 옵저버들에게 알림
  measurementsChanged() {
    this.notifyObserver()
  }

  // 실제 장비로부터 진짜 기상 데이터를 가져오는 대신 이 메서드를 통해 임의로 가정함
  setMeasurements(temperature: number, humidity: number, pressure: number) {
    this.temperature = temperature
    this.humidity = humidity
    this.pressure = pressure

    this.measurementsChanged()
  }
}

💻 타입스크립트 코드 구현 참고 : https://github.com/ckstn0777/Typescript-Design-Pattern/tree/main/ObserverPattern

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글