이 글은 영진닷컴 출판사에서 도서 리뷰를 요청하여 작성한 글입니다. 비록 출판사 리뷰 요청에 의해 작성한 글이지만 저는 이미 예전에 이 책을 수차례 완독하고 OOP 학습과 깊은 이해에 큰 도움일 받았던 책이였다는 점에서 개인적으로도 충분히 추천드리고 싶은 책이기도 합니다.
OOP 개념을 이제 막 배운 개발자라면 GoF의 디자인패턴 23가지를 반드시 학습해야 하는 이유는 OOP를 실제 코드에 효과적으로 적용한 체계적인 방법이 바로 디자인패턴이기 때문입니다. 디자인패턴에 대한 서적은 많지만 장황한 설명으로 난해하거나 이해하기 어려운데.. 하지만 “Java 언어로 배우는 디자인 패턴 입문”은 다자인패턴을 최대한 알기 쉽게 설명한 책 중 단연 으뜸입니다.
이 책은 2001년 1판을 시작으로 2022년에 3판으로 새롭게 업데이트 되어 출간되었습니다. 이 책은 디자인패턴을 Java라는 언어를 통해 설명하지만 Java에 특화된 OOP 코드 없이 일반적인 OOP 코드를 사용한 구체적인 예제의 구현을 중심으로 설명합니다. 결국 Java가 아니더라도 OOP의 기본을 완전하게 지원하는 다른 언어를 이용해서도 이 책에서 제시한 예제를 그대로 작성할 수 있습니다.
디자인패턴을 학습함에 있어서 반드시 알고 있어어 하는 것은 UML입니다. 즉, 소프트웨어의 설계도인데요. UML 중 클래스 다이어그램과 시퀀스 다이어그램은 소프트웨어의 설계를 위해 매우 핵심적인 표기를 위한 다이어그램입니다. 클래스 다이어그램은 클래스들 간의 관계도를 효과적으로 나타내며 시퀀스 다이어그램은 실행 순서를 클래스들 간의 소통과 함께 효과적으로 나타냅니다.
저 같은 경우 프로그램을 개발하고 유지보수 등을 이유로 오랜 시간이 흐른 뒤에 다시 해당 프로그램의 코드를 수정해야 할때 가장 먼저 살펴보는 것이 클래스 다이어그램입니다. 그만큼 클래스 다이어그램은 소프트웨어를 큰 그림으로 매우 효과적으로 이해하고 분석할 수 있는 설계도입니다. 그리고 좀더 구체적이고 세세한 부분을 분석할때는 시퀀스 다이어그램을 살펴봅니다.
이 책의 저자는 디자인패턴에 대한 구체적인 사례를 클래스다이어그램과 시퀀스 다이어그램으로 빠짐없이 제시하고 이들을 중심으로 한가지 명확한 사례를 실행 가능한 구체적인 코드로 작성합니다. 이 책에서 제시하는 23개의 패턴 중 State 패턴을 하나로 예로 들어보면 먼저 클래스 다이어그램은 다음과 같습니다. (원래 서적보다 좀더 간결하게 표기하였습니다)
이 책에서 언급하는 State 패턴은 어떤 사물의 상태를 클래스로 표현하여 각 상태에 따른 처리를 매우 효과적으로 구분하여 구현 할 수 있는 패턴입니다. 이 패턴을 사용하면 상태에 대한 처리를 if 문으로 분기하지 않고 클래스로 나눠 구현함으로써 코드가 매우 효과적으로 분리되어 관리될 수 있어 새로운 상태의 추가가 발생할 경우 이 State 패턴의 가치가 극대화 됩니다. 위의 클래스다이어그램은 이 State 패턴을 금고 경비 시스템의 구현 사례를 타나낸 것이고 금고를 사용함에 있어 낯일 경우(DayState 클래스)와 밤일 경우(NightState)에 따라 그 처리를 분리한 것입니다.
이 책에서는 이 금고 경비 시스템을 사례로 State 패턴으로 효과적으로 설명하기 위해 사용한 언어는 Java 코드이지만 앞어 언급했던 것처럼 OOP의 기본적인 내용을 충실하게 제공하는 언어라면 이 책의 코드를 자신이 원하는 언어로 바꿔 표현하는 것도 충분히 가능합니다. 저 같은 경우 Java가 아닌 이제는 대세로 자리잡은 웹 개발 언어인 TypeScript로 완전히 동일하게 구현해 봤는데.. 이 책의 Java 코드를 전혀 다른 언어로 동일한 시스템을 구현해 보면서 재미는 물론 해당 언어의 OOP에 대한 더욱 깊이 있는 이해가 가능했습니다.
이 책에서 언급한 State 패턴에 대한 구현 클래스를 TypeScript로 표현한 코드를 모두 언급해 보면 다음과 같습니다.
먼저 상태를 나타내는 인터페이스 코드는 다음과 같습니다. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { Context } from "./Context"; export interface State { doClock(context: Context, hour: number): void; doUse(context: Context): void; doAlarm(context: Context): void; doPhone(context: Context): void; }
그리고 이 State 인터페이스를 통해 낯 상태를 나타내는 DayState 클래스는 다음과 같습니다. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { Context } from "./Context"; import { State } from "./State"; import { NightState } from "./NightState"; export class DayState implements State { private static singleton: DayState = new DayState(); private constructor() {} public static getInstance():State { return this.singleton; } doClock(context: Context, hour: number): void { if(hour < 9 || 17 <= hour) { context.changeState(NightState.getInstance()); } } doUse(context: Context): void { context.recordLog("금고 사용(주간)"); } doAlarm(context: Context): void { context.callSecurityCenter("비상벨(주간)"); } doPhone(context: Context): void { context.callSecurityCenter("일반 통화(주간)"); } public toString(): String { return "[주간]"; } }
밤 상태는 다음과 같구요. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { Context } from "./Context"; import { State } from "./State"; import { DayState } from "./DayState"; export class NightState implements State { private static singleton: NightState = new NightState(); private constructor() {} doClock(context: Context, hour: number): void { if (9 <= hour && hour < 17) { context.changeState(DayState.getInstance()); } } doUse(context: Context): void { context.callSecurityCenter("비상: 야간 금고 사용!"); } doAlarm(context: Context): void { context.callSecurityCenter("비상벨(야간)"); } doPhone(context: Context): void { context.recordLog("야간 통화 녹음"); } public static getInstance(): NightState { return this.singleton; } public toString(): String { return "[야간]"; } }
상태를 관리하고 전체적인 제어를 위한 Context 인터페이스는 다음과 같습니다. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { State } from "./State"; export interface Context { setClock(hour: number): void; changeState(state: State): void; callSecurityCenter(msg: String): void; recordLog(msg: String): void; }
그리고 Context 인터페이스로 구체적인 금고시스템을 구현한 SafeFrame은 다음과 같습니다. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { Context } from "./Context"; import { DayState } from "./DayState"; import { State } from "./State"; export class SafeFrame implements Context { private state: State = DayState.getInstance(); public constructor(title: string) { (document.querySelector(".title") as HTMLElement).innerText = title; document.querySelector(".use").addEventListener("click", this.onClickEvent.bind(this)); document.querySelector(".alram").addEventListener("click", this.onClickEvent.bind(this)); document.querySelector(".phone").addEventListener("click", this.onClickEvent.bind(this)); document.querySelector(".exit").addEventListener("click", this.onClickEvent.bind(this)); } onClickEvent(evt: Event) { const text = (evt.currentTarget as HTMLElement).innerText; if(text === "금고사용") { this.state.doUse(this); } else if(text == "비상벨") { this.state.doAlarm(this); } else if(text == "일반통화") { this.state.doPhone(this); } else if(text == "종료") { alert("시스템 종료"); document.body.innerHTML = ""; } } setClock(hour: number): void { const clockString = `현재 시간은 ${hour}시`; console.log(clockString); const domTime = document.querySelector(".time"); if(domTime) domTime.innerHTML = clockString; this.state.doClock(this, hour); } changeState(state: State): void { console.log(`${this.state}에서 ${state}로 상태가 변화했습니다.`); this.state = state; } callSecurityCenter(msg: String): void { const domOutput = document.querySelector(".output"); const domItem = document.createElement("div"); domItem.innerText = `call! ${msg}`; domOutput.appendChild(domItem); } recordLog(msg: String): void { const domOutput = document.querySelector(".output"); const domItem = document.createElement("div"); domItem.innerText = `recording ... ${msg}`; domOutput.appendChild(domItem); } }
위의 모든 클래스를 활용하여 실행 가능하도록 한 코드는 다음과 같구요. (책에서는 Java로 되어 있으나 이를 Typescript로 변환하였습니다)
import { SafeFrame } from "./SafeFrame"; class Main { public static run(): void { let frame: SafeFrame = new SafeFrame("State 패턴 샘플"); let hour = 0; setInterval(() => { frame.setClock(hour); hour++; if(hour > 23) hour = 0; }, 1000); } } Main.run();
실행 결과는 다음과 같습니다. (책에서는 Java의 UI로 실행되나 이를 웹브라우저에서 실행되도록 하였습니다)
이미 언급했지만 이 책은 디자인패턴을 매우 쉽게 효과적으로 설명하고 있습니다. 또한 GoF의 23개의 디자인패턴은 특정 언어에 종속적이지 않으므로 이 책에서 제시하는 Java언어가 아닌 다른 객체지향 언어를 통해 코드를 작성해보면 디자인패턴 뿐만 아니라 해당 언어의 OOP에 대한 깊이 있는 이해도 가능하다는 점이 이 책의 또 다른 장점입니다. 중급 이전의 개발자로써 소프트웨어 설계에 관심이 있고 디자인패턴을 학습하고자 하시는 분들에게 이 책을 추천드립니다. 특히 OOP의 개념을 이제 막 학습한 분들에게 실제 OOP를 실무나 실제 코드에 어떻게 적용하는지 깊은 이해를 돕는 책으로써 강력히 추천드립니다.